Eagerly computed properties

The following is a subclass of Hand, where total is a simple attribute that's computed eagerly as each card is added:

class Hand_Eager(Hand):

def __init__(
self,
dealer_card: BlackJackCard,
*cards: BlackJackCard
) -> None:
self.dealer_card = dealer_card
self.total = 0
self._delta_soft = 0
self._hard_total = 0
self._cards: List[BlackJackCard] = list()
for c in cards:
# Mypy cannot discern the type of the setter.
# https://github.com/python/mypy/issues/4167
self.card = c # type: ignore

@property
def card(self) -> List[BlackJackCard]:
return self._cards

@card.setter
def card(self, aCard: BlackJackCard) -> None:
self._cards.append(aCard)
self._delta_soft = max(aCard.soft - aCard.hard, self._delta_soft)
self._hard_total = self._hard_total + aCard.hard
self._set_total()

@card.deleter
def card(self) -> None:
removed = self._cards.pop(-1)
self._hard_total -= removed.hard
# Issue: was this the only ace?
self._delta_soft = max(c.soft - c.hard for c in self._cards)
self._set_total()

def _set_total(self) -> None:
if self._hard_total + self._delta_soft <= 21:
self.total = self._hard_total + self._delta_soft
else:
self.total = self._hard_total

The __init__() method of the Hand_Eager class initializes the eagerly computed total to zero. It also uses two other instance variables, _delta_soft, and _hard_total, to track the state of ace cards in the hand. As each card is placed in the hand, these totals are updated.

Each use of self.card looks like an attribute. It's actually a reference to the property method decorated with @card.setter. This method's parameter, aCard, will be the value on the right side of the = in an assignment statement. 

In this case, each time a card is added via the card setter property, the total attribute is updated.

The other card property decorated with @card.deleter eagerly updates the total attribute whenever a card is removed. We'll take a look at deleter in detail in the next section.

A client sees the same syntax between these two subclasses (Hand_Lazy() and Hand_Eager()) of Hand:

d = Deck() 
h1 = Hand_Lazy(d.pop(), d.pop(), d.pop()) 
print(h1.total) 
h2 = Hand_Eager(d.pop(), d.pop(), d.pop()) 
print(h2.total) 

In both cases, the client software simply uses the total attribute. The lazy implementation defers computation of totals until required, but recomputes them every time. The eager implementation computes totals immediately, and only recomputes them on a change to the hand. The trade-off is an important software engineering question, and the final choice depends on how the overall application uses the total attribute.

The advantage of using properties is that the syntax doesn't change when the implementation changes. We can make a similar claim for getter/setter method functions. However, getter/setter method functions involve extra syntax that isn't very helpful or informative. The following are two examples, one of which involves the use of a setter method, while the other uses the assignment operator:

obj.set_something(value) 
obj.something = value 

The presence of the assignment operator (=) makes the intent very plain. Many programmers find it clearer to look for assignment statements than to look for setter method functions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset