The comparison operator methods

Python has six comparison operators. These operators have special method implementations. According to the documentation, mapping works as follows:

  • x<y is implemented by x.__lt__(y).
  • x<=y is implemented by x.__le__(y).
  • x==y is implemented by x.__eq__(y).
  • x!=y is implemented by x.__ne__(y).
  • x>y is implemented by x.__gt__(y).
  • x>=y is implemented by x.__ge__(y).

We'll return to comparison operators again when looking at numbers in Chapter 8, Creating Numbers.

There's an additional rule regarding what operators are actually implemented that's relevant here. These rules are based on the idea that the object's class on the left-hand side of an operator defines the required special method. If it doesn't, Python can try an alternative operation by changing the order and considering the object on the right-hand side of the operator.

Here are the two basic rules
First, the operand on the left-hand side of the operator is checked for an implementation: A<B means A.__lt__(B).
Second, the operand on the right-hand side of the operator is checked for a reversed implementation: A<B means B.__gt__(A).
The rare exception to this occurs when the right-hand operand is a subclass of the left-hand operand; then, the right-hand 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: int, suit: Suit) -> None:
self.rank = rank
self.suit = suit

def __lt__(self, other: Any) -> bool:
print(f"Compare {self} < {other}")
return self.rank < cast(BlackJackCard_p, other).rank

def __str__(self) -> str:
return f"{self.rank}{self.suit}"

This follows the Blackjack comparison rules, where suits don't matter and cards are only compared by their rank. We've omitted all but one of the comparison methods to see how Python will fall back when an operator is missing. This class will allow us to perform < comparisons. Interestingly, Python can also use this to perform > comparisons by switching the argument order. In other words, . This is the mirror reflection rule; we'll see it again in Chapter 8, Creating Numbers.

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

>>> two = BlackJackCard_p(2, Suit.Spade)
>>> three = BlackJackCard_p(3, Suit.Spade)
>>> two < three
Compare 2♠ < 3♠
True
>>> two > three
Compare 3♠ < 2♠
False
>>> two == three
False

This example shows that a comparison using the < operator is implemented by the defined __lt__() method, as expected. When using the > operator, then the available __lt__() method is also used, but with the operands reversed.

What happens when we try to use an operator such as <=? This shows the exception:

>>> two <= three
Traceback (most recent call last):
File "/Users/slott/miniconda3/envs/py37/lib/python3.7/doctest.py", line 1329, in __run
compileflags, 1), test.globs)
File "<doctest __main__.__test__.test_blackjackcard_partial[5]>", line 1, in <module>
print("{0} <= {1} :: {2!r}".format(two, three, two <= three)) # doctest: +IGNORE_EXCEPTION_DETAIL
TypeError: '<=' not supported between instances of 'BlackJackCard_p' and '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. You will recall that the default implementation compares the object IDs and all unique objects will compare as not equal. The objects participate in == tests as follows:

>>> two_c = BlackJackCard_p(2, Suit.Club)
>>> two_c == BlackJackCard_p(2, Suit.Club)
False

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

There's no logical connection among the operators either. 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:

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

The @functools.total_ordering decorator can help overcome the default limitation. This decorator deduces the rest of the comparisons from just __eq__() and one of these: __lt__(), __le__(), __gt__(), or __ge__(). This provides all the necessary comparisons. We'll revisit this in Chapter 8, Creating Numbers.

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

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