Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is d1==d2 not equivalent to d1.__eq__(d2)?

According to the docs (in Python 3.8):

By default, object implements __eq__() by using is, returning NotImplemented in the case of a false comparison: True if x is y else NotImplemented.

And also:

The correspondence between operator symbols and method names is as follows: [...] x==y calls x.__eq__(y)

So I expect

  1. == to be equivalent to __eq__() and
  2. a custom class without an explicitly defined __eq__ to return NotImplemented when using == to compare two different instances of the class. Yet in the following, == comparison returns False, while __eq__() returns NotImplemented:
class Dummy():
    def __init__(self, a):
        self.a = a

d1 = Dummy(3)
d2 = Dummy(3)

d1 == d2 # False
d1.__eq__(d2) # NotImplemented

Why?

like image 583
the.real.gruycho Avatar asked Sep 15 '25 05:09

the.real.gruycho


1 Answers

The reason is that if one side of the operation cannot (or will not) provide an answer, the other side is allowed a say in handling this comparison. A common example is float/int comparisons:

>>> 1 == 1.0
True
>>> (1).__eq__(1.0)
NotImplemented
>>> (1.0).__eq__(1)
True

With int and float, neither is a subclass of the other, and an int doesn't have anything to say about whether it's equal to some float or not. The equality comparison gets handled by the float type.

It's easier to understand if you have distinct types for the left and right sides, and add some debug printout in the hooks:

class Left:
    def __eq__(self, other):
        print("trying Left.__eq__")
        return NotImplemented

class Right:
    def __eq__(self, other):
        print("trying Right.__eq__")
        return True

Demo:

>>> d1 = Left()
>>> d2 = Right()
>>> d1.__eq__(d2)
trying Left.__eq__
NotImplemented
>>> d2.__eq__(d1)
trying Right.__eq__
True
>>> d1 == d2  # Left.__eq__ "opts out" of the comparison, Python asks other side
trying Left.__eq__
trying Right.__eq__
True
>>> d2 == d1  # Right.__eq__ returns a result first, Left.__eq__ isn't considered
trying Right.__eq__
True

Left side type(d1).__eq__ opts out by returning NotImplemented, which allows the right hand side a "second chance" at handling the operation. If left side had returned False instead of returning NotImplemented, Python wouldn't attempt the right side at all and the result would of d1 == d2 be False. If both sides return NotImplemented, like in your Dummy example, then the objects are considered unequal unless they are identical (i.e. same instance).

like image 195
wim Avatar answered Sep 16 '25 18:09

wim