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

Tip

Here are the two basic rules

First, the operand on the left is checked for an operator implementation: A<B means A.__lt__(B).

Second, the operand on the right is checked for a reversed operator implementation: A<B means B.__gt__(A).

The rare exception to this occurs when the right operand is a subclass of the left operand; then, the right operand is checked first to allow a subclass to override a superclass.

We can see how this works by defining a class with only one of the operators defined and then using it for other operations.

The following is a partial class that we can use:

class BlackJackCard_p:
    def __init__( self, rank, suit ):
        self.rank= rank
        self.suit= suit
    def __lt__( self, other ):
        print( "Compare {0} < {1}".format( self, other ) )
        return self.rank < other.rank
    def __str__( self ):
        return "{rank}{suit}".format( **self.__dict__ )

This follows the Blackjack comparison rules where suits don't matter. We've omitted comparison methods to see how Python will fallback when an operator is missing. This class will allow us to perform the < comparisons. Interestingly, Python can also use this to perform the > comparisons by switching the argument order. In other words, x<y≡y>x. This is the mirror reflection rule; we'll see it again in Chapter 7, Creating Numbers.

We see this when we try to evaluate different comparison operations. We'll create two Cards classes and compare them in various ways as shown in the following code snippet:

>>> two = BlackJackCard_p( 2, '♠' )
>>> three = BlackJackCard_p( 3, '♠' )
>>> two < three
Compare 2♠ < 3♠
True
>>> two > three
Compare 3♠ < 2♠
False
>>> two == three
False
>>> two <= three
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: BlackJackCard_p() <= BlackJackCard_p()

From this, we can see where two < three maps to two.__lt__(three).

However, for two > three, there's no __gt__() method defined; Python uses three.__lt__(two) as a fallback plan.

By default, the __eq__() method is inherited from object; it compares the object IDs; the objects participate in == and != tests as follows:

>>> two_c = BlackJackCard_p( 2, '♣' )
>>> two == two_c
False

We can see that the results aren't quite what we expect. We'll often need to override the default implementation of __eq__().

Also, there's no logical connection among the operators. Mathematically, we can derive all the necessary comparisons from just two. Python doesn't do this automatically. Instead, Python handles the following four simple reflection pairs by default:

The comparison operator methods
The comparison operator methods
The comparison operator methods
The comparison operator methods

This means that we must, at the minimum, provide one from each of the four pairs. For example, we could provide __eq__(), __ne__(), __lt__(), and __le__().

The @functools.total_ordering decorator overcomes the default limitation and deduces the rest of the comparisons from just __eq__() and one of these: __lt__(), __le__(), __gt__(), or __ge__(). We'll revisit this in Chapter 7, Creating Numbers.