abukaj
abukaj

Reputation: 2712

Why is `int.__eq__(other)` a working comparison?

The following code works with Python 2.7:

>>> class Derived(int):
...     def __eq__(self, other):
...         return int.__eq__(other)
...
>>> Derived(12) == 12.0
True
>>> Derived(12) == 13
False

I do not understand, why it works, given that the self attribute is not explicitly given to int.__eq__() method call.

[EDIT]

Answers so far suggested, that it is about returning NotImplemented by self.__eq__(other) and thus calling other.__eq__(self). Then Derived(12) == Derived(12) I expect to be an infinitive recursion, which is not the case:

>>> Derived(12) == Derived(12)
True

Upvotes: 1

Views: 1090

Answers (3)

Billy
Billy

Reputation: 5609

In Python 2.7, if you call int.__eq__ it always returns NotImplemented. Example:

>>> int.__eq__(12.0)
NotImplemented

When you use the == operator it will attempt to run the __eq__ method on the left argument, and if it gets NotImplemented it will return the result of the __eq__ method from the argument on the right.

In your example for Derived(12) == 12.0, the interpreter first tries Derived(12).__eq__(12.0), and gets NotImplemented. It then runs the __eq__ method on the float number 12.0 and gets True.

In the case of your Derived(12) == Derived(12) example, what's likely happening is that since both objects return NotImplemented for their __eq__ methods, and since Derived inherits from int, the interpreter falls back to using the cmp builtin behavior for int (according to this answer, which is linked-to in another answer to your question).

Here's an example that illustrates your case:

class Derived(int):
    def __eq__(self, other):
        print 'Doing eq'
        return NotImplemented
    def __cmp__(self, other):
        print 'doing cmp'
        return 0  # contrived example - don't do this

>>> Derived(12) == Derived(12)
doing eq
doing eq
doing cmp
True

Upvotes: 0

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 251106

It works because int.__eq__(<something>) returns NotImplemented and when that happens it results in a call to other.__eq__(self) and that's what is returning True and False here.

Demo:

class Derived(int):
     def __eq__(self, other):
         print self, other
         print int.__eq__(other)
         print other.__eq__(self)
         return int.__eq__(other)

>>> Derived(12) == 12.0
12 12.0
NotImplemented
True
True
>>> Derived(12) == 13.0
12 13.0
NotImplemented
False
False

From NotImplemented 's docs:

Special value which should be returned by the binary special methods (e.g. __eq__(), __lt__(), __add__(), __rsub__(), etc.) to indicate that the operation is not implemented with respect to the other type; may be returned by the in-place binary special methods (e.g. __imul__(), __iand__(), etc.) for the same purpose. Its truth value is true.

Note When NotImplemented is returned, the interpreter will then try the reflected operation on the other type, or some other fallback, depending on the operator. If all attempted operations return NotImplemented, the interpreter will raise an appropriate exception.


What happens when both __eq__ return NotImplemented?

The behaviour is different in Python 2 and 3.

In Python 2 it falls back to __cmp__ method first and integers have __cmp__ method in Python 2. It has been removed in Python 3.

As per Python 2 docs if nothing is found it ultimately falls back to identity comparison:

If no __cmp__(), __eq__() or __ne__() operation is defined, class instances are compared by object identity (“address”)

class Derived(int):
    def __eq__(self, other):
        print ("Inside  __eq__")
        return NotImplemented

    def __cmp__(self, other):
        print ("Inside __cmp__ finally")
        return True

>>> Derived(12) == Derived(12)
Inside  __eq__
Inside  __eq__
Inside __cmp__ finally
False

Not let's define a class with no method defined:

class Derived(object):
    pass

>>> Derived() == Derived()  
False
>>> d = Derived()
>>> d == d  # Same objects.
True

Python 3 doesn't have __cmp__ method anymore but it seems to be falling back to identity now. And it seems it is not documented either.

#  Python 3.5
>>> Derived() == Derived()
False
>>> d = Derived()
>>> d == d
True

Upvotes: 5

s.gaynetdinov
s.gaynetdinov

Reputation: 74

When mixing float with an integer type, there's no good uniform approach. https://github.com/python/cpython/blob/2.7/Objects/floatobject.c#L401-L417

P.S. How int() object using "==" operator without __eq__() method in python2?

Upvotes: 1

Related Questions