What's the result of returning NotImplemented
from __eq__
special method in python 3 (well 3.5 if it matters)?
The documentation isn't clear; the only relevant text I found only vaguely refers to "some other fallback":
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 returnNotImplemented
, the interpreter will raise an appropriate exception. See Implementing the arithmetic operations for more details.
Unfortunately, the "more details" link doesn't mention __eq__
at all.
My reading of this excerpt suggests that the code below should raise an "appropriate exception", but it does not:
class A:
def __eq__(self, other):
return NotImplemented
class B:
def __eq__(self, other):
return NotImplemented
# docs seems to say these lines should raise "an appropriate exception"
# but no exception is raised
a = A()
b = B()
a == b # evaluates as unequal
a == a # evaluates as equal
From experimenting, I think that when NotImplemented
is returned from __eq__
, the interpreter behaves as if __eq__
wasn't defined in the first place (specifically, it first swaps the arguments, and if that doesn't resolve the issue, it compares using the default __eq__
that evaluates "equal" if the two objects have the same identity). If that's the case, where in the documentation can I find the confirmation of this behavior?
Edit: see Python issue 28785
Actually the ==
and !=
check work identical to the ordering comparison operators (<
and similar) except that they don't raise the appropriate exception but fall-back to identity comparison. That's the only difference.
This can be easily seen in the CPython source code (version 3.5.10). I will include a Python version of that source code (at least as far as it's possible):
_mirrored_op = {'__eq__': '__eq__', # a == b => b == a
'__ne__': '__ne__', # a != b => b != a
'__lt__': '__gt__', # a < b => b > a
'__le__': '__ge__', # a <= b => b >= a
'__ge__': '__le__', # a >= b => b <= a
'__gt__': '__lt__' # a > b => b < a
}
def richcmp(v, w, op):
checked_reverse = 0
# If the second operand is a true subclass of the first one start with
# a reversed operation.
if type(v) != type(w) and issubclass(type(w), type(v)) and hasattr(w, op):
checked_reverse = 1
res = getattr(w, _mirrored_op[op])(v) # reversed
if res is not NotImplemented:
return res
# Always try the not-reversed operation
if hasattr(v, op):
res = getattr(v, op)(w) # normal
if res is not NotImplemented:
return res
# If we haven't already tried the reversed operation try it now!
if not checked_reverse and hasattr(w, op):
res = getattr(w, _mirrored_op[op])(v) # reversed
if res is not NotImplemented:
return res
# Raise exception for ordering comparisons but use object identity in
# case we compare for equality or inequality
if op == '__eq__':
res = v is w
elif op == '__ne__':
res = v is not w
else:
raise TypeError('some error message')
return res
and calling a == b
then evaluates as richcmp(a, b, '__eq__')
. The if op == '__eq__'
is the special case that makes your a == b
return False
(because they aren't identical objects) and your a == a
return True
(because they are).
However the behavior in Python 2.x was completely different. You could have up to 4 (or even 6, I don't remember exactly) comparisons before falling back to identity comparison!
Not sure where (or if) it is in the docs, but the basic behavior is:
__eq__(lhs, rhs)
NotImplemented
return it__eq__(rhs, lhs)
NotImplemented
return iteq -> same objects? -> True, else False
ne -> different objects? -> True, else False
many others -> raise exception
The reason that eq
and ne
do not raise exceptions is:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With