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
NotImplementedis 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