简体   繁体   中英

Returning NotImplemented from __eq__

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; theonly 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 return NotImplemented , 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:

  • try the operation: __eq__(lhs, rhs)
  • if result is not NotImplemented return it
  • else try the reflected operation: __eq__(rhs, lhs)
  • if result is not NotImplemented return it
  • otherwise use appropriate fall back:

    eq -> 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:

  • they can always be determined (apple == orange? no)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM