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.