A mixed class comparison example

Given a definition of a total for a Hand object, we can meaningfully define comparisons between Hand instances and comparisons between Hand and int. In order to determine which kind of comparison we're doing, we're forced to use isinstance().

The following is a partial definition of Hand with comparisons. Here's the first part:

class Hand:

def __init__(self, dealer_card: Card2, *cards: Card2) -> None:
self.dealer_card = dealer_card
self.cards = list(cards)

def __str__(self) -> str:
return ", ".join(map(str, self.cards))

def __repr__(self) -> str:
cards_text = ", ".join(map(repr, self.cards))
return f"{self.__class__.__name__}({self.dealer_card!r}, {cards_text})"

Here's the second part, emphasizing the comparison methods:

def __eq__(self, other: Any) -> bool:
if isinstance(other, int):
return self.total() == other
try:
return (
self.cards == cast(Hand, other).cards
and self.dealer_card == cast(Hand, other).dealer_card
)
except AttributeError:
return NotImplemented

def __lt__(self, other: Any) -> bool:
if isinstance(other, int):
return self.total() < cast(int, other)
try:
return self.total() < cast(Hand, other).total()
except AttributeError:
return NotImplemented

def __le__(self, other: Any) -> bool:
if isinstance(other, int):
return self.total() <= cast(int, other)
try:
return self.total() <= cast(Hand, other).total()
except AttributeError:
return NotImplemented

def total(self) -> int:
delta_soft = max(c.soft - c.hard for c in self.cards)
hard = sum(c.hard for c in self.cards)
if hard + delta_soft <= 21:
return hard + delta_soft
return hard

We've defined three of the comparisons, not all six. Python's default behavior can fill in the missing operations. Because of the special rules for different types, we'll see that the defaults aren't perfect.

In order to interact with Hands, we'll need a few Card objects:

>>> two = card21(2, '♠') 
>>> three = card21(3, '♠') 
>>> two_c = card21(2, '♣') 
>>> ace = card21(1, '♣') 
>>> cards = [ace, two, two_c, three] 

We'll use this sequence of cards to see two different hand instances.

This first Hands object has an irrelevant dealer's Card object and the set of four Cards created previously. One of the Card objects is an ace:

>>> h = Hand(card21(10,'♠'), *cards) 
>>> print(h) A♣, 2♠, 2♣, 3♠ >>> h.total() 18

The total of 18 points is a soft total, because the ace is being treated as having 11 points. The hard total for these cards is 8 points.

The following is a second Hand object, which has an additional Card object:

>>> h2 = Hand(card21(10,'♠'), card21(5,'♠'), *cards) 
>>> print(h2) 
5♠, A♣, 2♠, 2♣, 3♠ 
>>> h2.total() 
13 

This hand has a total of 13 points. This is a hard total. The soft total would be over 21, and therefore irrelevant for play.

Comparisons between Hands work very nicely, as shown in the following code snippet:

>>> h < h2 
False 
>>> h > h2 
True 

These comparisons mean that we can rank Hands based on the comparison operators. We can also compare Hands with integers, as follows:

>>> h == 18 
True 
>>> h < 19 
True 
>>> h > 17 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
TypeError: unorderable types: Hand() > int() 

The comparisons with integers work as long as Python isn't forced to try a fallback. The h > 17 example shows us what happens when there's no __gt__() method. Python checks the reflected operands, and the integer, 17, doesn't have a proper __lt__() method for Hand either.

We can add the necessary __gt__() and __ge__() functions to make Hand work properly with integers. The code for these two comparisons is left as an exercise for the reader.

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

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