Can shorthand operators be used with getters and setters? - python

Is it possible to set a property with shorthand operators like +=, or -=?
I've read this post, What's the pythonic way to use getters and setters?, but it deals mainly with direct assignment in terms of object.attr = value. When thinking about it, I'm finding it difficult to be possible and I've never used setters so I'm not sure.
It is within the Player.hit() method where I'm attempting to achieve this where I'm subtracting and integer reduced_points from another integer value property:
self.hands[x].value -= reduced_points
NOTE: Hand.value is an #property due to that I need to dynamically add the values of each card in case a card is added to that hand.
class Card:
def __init__(self, pip, suit, *args, **kwargs):
self.pip = pip
self.suit = suit
if kwargs and 'face_card' in kwargs:
self.face_card = kwargs['face_card']
class Hand:
def __init__(self, cards):
self.cards = cards # [Card(), Card(), Card()]
self._value = sum(card.pip for card in cards)
#property
def soft(self):
return any(
hasattr(card, 'face_card') and
card.face_card == "Ace" for card in self.cards
)
#property
def value(self):
return sum([card.pip for card in self.cards])
#value.setter
def set_value(self, value):
new_value = self._value - value
self._value = new_value
class Player:
def __init__(self):
self.name = "Player"
self.hands = []
self.chips = 0
self._bet = 0
def hit(self, hand):
if hand.soft:
x, hand = list(filter(
lambda h: h[1].soft, enumerate(self.hands)
))[0]
total_aces = len(
list(
filter(lambda c: hasattr(c, 'face_card') and
c.face_card == "Ace", self.hands[x].cards)
)
)
reduced_points = 10 * (total_aces - 1)
self.hands[x].value -= reduced_points
if self.hands[x].value > 21:
return self.hands[x], "BUST"
if hand.value > 21:
return hand , "BUST"
return hand, "HIT"

When creating a property setter, it needs to have the same name as the getter. So both the getter and setter needed to be def value(...). But yes, hand.value -= 3 should do exactly what you expect it to.

Related

elegant way to pass operator to attribute in python

Basically my Question: Is there an elegant way, if I have a given Container class
class Container:
def __init__(self, value):
self.value = value
to pass any operator to the value parameter of the Container class?
So one obvious solution would be to override any single operator on its own. So for example for + I could do:
class Container:
def __init__(self, value):
self.value = value
def __add__(self, other):
return self.value + other
so that for example
c1 = Container(1)
c1 += 1
print(c1)
would result in
2
and
c2 = Container("ab")
c2 += "c"
print(c2)
would result in
abc
So what I could do is to override all arithmetic built-in functions from python. But my Question is, is there a more elegant (shorter) way to do this for any operator?
You can factor out some of the more repetitive boilerplate:
import operator
class Container:
def __init__(self, value):
self.value = value
def _generic_op(f):
def _(self, other):
return f(self.value, other)
return _
__add__ = _generic_op(operator.add)
__sub__ = _generic_op(operator.sub)
__mul__ = _generic_op(operator.mul)
# etc
# No need for _generic_op as a class attribute
del _generic_op
If the number of overloads are smaller than the different classes you want to overload in, you can define saperate class for each overload and inherit it. So,
class Adder:
def __add__(self, val):
self.value += val
return self
class Subtractor:
def __sub__(self, val):
self.value -= val
return self
class Foo(Adder, Subtractor):
def __init__(self, value):
self.value = value
foo = Foo(5)
foo -= 3
foo += 7
print(foo.value)
But I am against doing such operations between custom DataTypes and native dataTypes. Consider this just a simple example that doesn't follow good coding principles in that sense.
If incase you still want to allow adding int/float directly, you might also want to handle Foo class objects inside too.

not all attributes defined in __init__?

I've seen the following code.
class Primes:
def __init__(self, max):
self.max = max
self.number = 1
def __iter__(self):
return self
def __next__(self):
self.number += 1
if self.number >= self.max:
raise StopIteration
elif check_prime(self.number):
return self.number
else:
return self.__next__()
In the dunder init function, we set self.number=1, without having included earlier the attribute number. What's the meaning of it?
This code, only means that self.number is not customizable and will always values 1 when creating an instance of Primes. This is used when the class need an attribute, which will be used along its state and methods, but it's value should always be the same when instanciating the object
def __init__(self, max):
self.max = max
self.number = 1

Using property method with setter prevents function from binding to object attribute

I am attempting to use a property-based method to control the class constructor (screening for bad values at instance creation time), in this code. For some reason I don't understand the function set_sequence won't alter the derived class Binary object's attribute seq when called from the #num.setter method - it has to be called elsewhere. Am I missing something basic about how properties work?
class Number:
def __init__(self, num):
self.num = num
class Binary(Number):
def __init__(self, num):
super().__init__(num)
self.seq = ()
#property
def num(self):
return self._num
#num.setter
def num(self, value):
if value < 0:
raise ValueError('Unsigned numbers cannot be negative')
self._num = value
self.set_sequence() #this calls the function, but the value doesn't get bound
def set_sequence(self):
print('called ', end='')
self.seq = tuple([int(x) for x in bin(self.num)[2:]])
Calling this code as follows:
if __name__ == "__main__":
n1 = Binary(11)
print(f"{n1.num} = {n1.seq}")
n1.set_sequence()
print(f"{n1.num} = {n1.seq}")
Gives:
called 11 = ()
called 11 = (1, 0, 1, 1)
This throws an exception as expected when negative values are passed to constructor, but I don't understand why the function call fails to behave as expected. This pattern is based on Brett Slatkin's Item#29 in 'Effective Python' incidentally, the part about using #property to do type checking and value validation when the constructor is called.
Because in your constructor after super().__init__(num) that calls your #num.setter you use self.seq = () that overrides the value stored in your setter method.
To have the desired output, you should do like this. In you example self.set_sequence() is overridden by the second instruction in the constructor.
class Number:
def __init__(self, num):
self.num = num
class Binary(Number):
seq = ()
def __init__(self, num):
# or eventually here
# seq = ()
super().__init__(num)
#property
def num(self):
return self._num
#num.setter
def num(self, value):
if value < 0:
raise ValueError('Unsigned numbers cannot be negative')
self._num = value
self.set_sequence() #this calls the function, but the value doesn't get bound
def set_sequence(self):
print('called ', end='')
self.seq = tuple([int(x) for x in bin(self.num)[2:]])
if __name__ == "__main__":
n1 = Binary(11)
print(f"{n1.num} = {n1.seq}")
n1.set_sequence()
print(f"{n1.num} = {n1.seq}")

Repeat class method in Python

I would like to repeat a class method several times (26 to be exact) so that I can split a deck of cards to two separate decks. I tried [:26] and [26:] (obviously for lists only) in addition to d1=Deck() then deck1 = d1.deal()*26 and they both did not work. I just learned how to create Classes.
Desired output is deck1=["""26 random cards"""] and deck2=["""26 random cards"""].
Relevant code:
import random
suits = ['H','C','S','D']
ranks = ['A']+list(map(str,range(2,10)))+['X','J','Q','K']
values = [14]+list(range(2,14))
class Card:
def __init__(self,suit,rank):
self.suit=suit
self.rank=rank
self.value=values[ranks.index(self.rank)]
def __str__(self):
return "*------*\n|{0}{1} |\n| |\n| {0}{1}|\n*------*".format(self.rank,self.suit)
def __gt__(self,other):
return self.value > other.value
def __lt__(self,other):
return self.value < other.value
def __eq__(self,other):
return self.value == other.value
class Deck:
"""This class is for creating deck"""
def __init__(self):
self.deck=[]
for suit in suits:
for rank in ranks:
self.deck.append(Card(suit,rank))
self.shuffle()
def __str__ (self):
tmp=''
for card in self.deck:
tmp+=str(card)+'\n'
return tmp
def shuffle(self):
random.shuffle(self.deck)
def deal(self):
return self.deck.pop()
So the order of operations is going to execute your function once, then repeat the result 26 times. You probably want something more like:
deck1 = [d1.deal() for _ in range(26)]
Whenever you want to repeat an action a certain number of times, use a for loop.
d2 = []
for i in range(26):
d2.append(d1.deal())
There's no way for your code at the moment to create an empty deck. Maybe you should have a __init__ that creates an empty deck and then a separate method to make a full deck? Just a thought.

How should my __lt__ code look like if I want .sort() to work on my list of classes

I have created a carddeck and every player is assigned a hand. I would like the players to be able to arrange their hand with the lowest value first and then print out this hand. I would like to think this can be done with .sort() but atm I'm getting a message
TypeError: unorderable types: NumberedCard() < NumberedCard()
I got a tip from the net that if I defined how .sort() is to work through a correct def __lt__(self, other): then I wouldn't be having this problem. So help me programmers figure this one out. Every card is defined by value and suit.
My code looks like this:
import random
suitlist=["Hearts","Spades","Clubs","Diamonds"]
class NumberedCard():
def __init__(self,value,suit):
self.value=value
self.suit=suit
def __str__(self):
return '%s of %s' %(self.value,suitlist[self.suit])
class JackCard():
def __init__(self,suit):
self.suit=suit
self.value=11
def __str__(self):
return 'Jack of %s' %(suitlist[self.suit])
class QueenCard():
def __init__(self,suit):
self.suit=suit
self.value=12
def __str__(self):
return 'Queen of %s' %(suitlist[self.suit])
class KingCard():
def __init__(self,suit):
self.suit=suit
self.value=13
def __str__(self):
return 'King of %s' %(suitlist[self.suit])
class AceCard():
def __init__(self,suit):
self.suit=suit
self.value=14
def __str__(self):
return 'Ace of %s' %(suitlist[self.suit])
class StandardDeck():
"""A class that creates a deck of 52 different cards, that also gives options to do matching operations with them"""
def __init__(self):
self.deck = []
for suit in range(4):
for value in range(2,11):
card = NumberedCard(value,suit)
self.deck.append(card)
self.deck.append(JackCard(suit))
self.deck.append(QueenCard(suit))
self.deck.append(KingCard(suit))
self.deck.append(AceCard(suit))
#for k in range(52):
# print(self.deck[k])
def shuffle(self):
"""Shake n' bake with the cards."""
random.shuffle(self.deck)
def take_card(self):
return self.deck.pop()
def deal_cards(self, hand,num):
for i in range(num):
hand.add_card(self.take_card())
class Hand:
"""A class to do the usual poker things during the game. Ie pick up cards, drop cards etc."""
def __init__(self):
self.hand = []
def __str__(self):
Handen=""
for card in self.hand:
Handen+=str(card) + "\n"
return Handen
def add_card(self,card):
self.hand.append(card)
def sort_cards(self):
self.hand.sort()
####Testing the program
deck=StandardDeck()
deck.shuffle()
hand=Hand()
hand.add_card(deck.take_card())
hand.add_card(deck.take_card())
print(hand)
hand.sort_cards() # <---- This is the part not working
So how is it done and where should it be put?
The best way to salvage this would be to create a class Card which would have a value and the __lt__ or __gt__ and a __eq__ defined method. And then proliferate those method by having all the other cards inherit from that parent class.
The proposed methods could be as simple as just compairing values of value which is an int and python knows how to do it himself.
Something like this:
>>> class Card(object):
def __init__(self, value):
self.value = value
def __lt__(self, other):
if other.value > self.value:
return True
return False
>>> class Ace(Card):
def __init__(self, value):
super().__init__(value)
>>> class NumCard(Card):
def __init(self, value):
super().__init__(value)
>>> jack = Ace(12)
>>> n = NumCard(7)
>>> jack > n
True
>>> n > jack
False
If you only want to sort the list in self.hand why not just use the key argument for sort to create a function to return the value to sort on?
self.hand.sort(key=lambda x: x.value)
This link should provide some info on sorting techniques

Categories

Resources