Search code examples
pythoncpythoncomparison-operatorsmagic-methods

Why and when do literal comparison operators like `==` in Python use the magic method of a custom type over a builtin?


The docs.python.org page on the Python "Data Model" states that when both sides in a literal comparison operation implement magic methods for the operation, the method of the left operand gets used with the right operand as its argument:

x<y calls x.__lt__(y), x<=y calls x.__le__(y), x==y calls x.__eq__(y), x!=y calls x.__ne__(y), x>y calls x.__gt__(y), and x>=y calls x.__ge__(y).

The following class wraps the builtin tuple and implements a magic method for one of these comparison operators to demonstrate this:

class eqtest(tuple):
 def __eq__(self, other):
  print('Equivalence!')

When using instances of this class on the left side of a comparison operator, it behaves as expected:

>>> eqtest((1,2,3)) == (1,2,3)
Equivalence!

However, the comparison operator of the custom class seems to get called even when only using its instance on the right:

>>> (1,2,3) == eqtest((1,2,3))
Equivalence!

The result is also demonstrably different when the magic method of the left operand is explicitly called:

>>> (1,2,3).__eq__(eqtest2((1,2,3)))
True

It's easy to understand why this might be a deliberate design choice, especially with subclasses, in order to return the result most likely to be useful from the type that was defined later. However, since it deviates quite explicitly from the basic documented behaviour, it's quite hard to know how and why it works this way confidently enough to account for and use it in production.

In what cases do the Python language and the CPython reference implementation reverse the order of comparison operators even if both sides provide valid results, and where is this documented?


Solution

  • The rules on comparisons state that tuples don't know how to compare to other types. tuplerichcompare does Py_RETURN_NOTIMPLEMENTED. However, the PyObject richcompare checks for subtypes, such as your inherited class, and swaps the comparison order (applying the symmetry rule).

    This is documented in the page you linked as well:

    If the operands are of different types, and right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority, otherwise the left operand’s method has priority. Virtual subclassing is not considered.

    This enables subclasses to implement more specific behaviours that work with the comparison written either way.