Mastering Objectoriented Python
上QQ阅读APP看书,第一时间看更新

A mixed class comparison example

Given a definition of a total for a Hand object, we can meaningfully define comparisons between the 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:

class Hand:
    def __init__( self, dealer_card, *cards ):
        self.dealer_card= dealer_card
        self.cards= list(cards)
    def __str__( self ):
        return ", ".join( map(str, self.cards) )
    def __repr__( self ):
        return "{__class__.__name__}({dealer_card!r}, {_cards_str})".format(
        __class__=self.__class__,
        _cards_str=", ".join( map(repr, self.cards) ),
        **self.__dict__ )
    
    def __eq__( self, other ):
        if isinstance(other,int):
            return self.total() == other
        try:
            return (self.cards == other.cards 
                and self.dealer_card == other.dealer_card)
        except AttributeError:
            return NotImplemented
    def __lt__( self, other ):
        if isinstance(other,int):
            return self.total() < other
        try:
            return self.total() < other.total()
        except AttributeError:
            return NotImplemented
    def __le__( self, other ):
        if isinstance(other,int):
            return self.total() <= other
        try:
            return self.total() <= other.total()
        except AttributeError:
            return NotImplemented
    __hash__ = None
    def total( self ):
        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.

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 soft total is 18 and the hard total is 8.

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

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

The hard total is 13. There's no soft total because it would be over 21.

The comparisons among Hands work very nicely, as shown in the following code snippet:

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

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 previous 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.