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

Implementation of comparison for objects of the same class

We'll look at a simple same-class comparison by looking at a more complete BlackJackCard class:

 class BlackJackCard:
    def __init__( self, rank, suit, hard, soft ):
        self.rank= rank
        self.suit= suit
        self.hard= hard
        self.soft= soft
    def __lt__( self, other ):
        if not isinstance( other, BlackJackCard ): return NotImplemented
        return self.rank < other.rank
    
    def __le__( self, other ):
        try:
            return self.rank <= other.rank
        except AttributeError:
            return NotImplemented
    def __gt__( self, other ):
        if not isinstance( other, BlackJackCard ): return NotImplemented
        return self.rank > other.rank
    def __ge__( self, other ):
        if not isinstance( other, BlackJackCard ): return NotImplemented
        return self.rank >= other.rank
    def __eq__( self, other ):
        if not isinstance( other, BlackJackCard ): return NotImplemented
        return self.rank == other.rank and self.suit == other.suit
    def __ne__( self, other ):
        if not isinstance( other, BlackJackCard ): return NotImplemented
        return self.rank != other.rank and self.suit != other.suit
    def __str__( self ):
        return "{rank}{suit}".format( **self.__dict__ )

We've now defined all six comparison operators.

We've shown you two kinds of type checking: explicit and implicit. The explicit type checking uses isinstance(). The implicit type checking uses a try: block. There's a tiny conceptual advantage to using the try: block: it avoids repeating the name of a class. It's entirely possible that someone might want to invent a variation on a card that's compatible with this definition of BlackJackCard but not defined as a proper subclass. Using isinstance() might prevent an otherwise valid class from working correctly.

The try: block might allow a class that coincidentally happens to have a rank attribute to work. The risk of this turning into a difficult-to-solve problem is nil, as the class would likely fail everywhere else it was used in this application. Also, who compares an instance of Card with a class from a financial modeling application that happens to have a rank-ordering attribute?

In future examples, we'll focus on the try: block. The isinstance() method check is idiomatic Python and is widely used. We explicitly return NotImplemented to inform Python that this operator isn't implemented for this type of data. Python can try reversing the argument order to see if the other operand provides an implementation. If no valid operator can be found, then a TypeError exception will be raised.

We omitted the three subclass definitions and the factory function, card21(). They're left as an exercise.

We also omitted intraclass comparisons; we'll save that for the next section. With this class, we can compare cards successfully. The following is an example where we create and compare three cards:

>>> two = card21( 2, '♠' )
>>> three = card21( 3, '♠' )
>>> two_c = card21( 2, '♣' )

Given those Cards classes, we can perform a number of comparisons as shown in the following code snippet:

>>> two == two_c
False
>>> two.rank == two_c.rank
True
>>> two < three
True
>>> two_c < three
True

The definitions seem to work as expected.