Returning NotImplemented from __eq__

▼魔方 西西 提交于 2020-08-19 06:08:46

问题


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


回答1:


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. 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__': '__ge__',  # a < b  => b >= a
                '__le__': '__gt__',  # a <= b => b > a
                '__ge__': '__lt__',  # a >= b => b < a
                '__gt__': '__le__'   # 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('sth')

    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 behaviour in Python 2.x was completly different. You could have up to 4 (or even 6, I don't remember exactly) comparisons before falling back to identity comparison!




回答2:


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)


来源:https://stackoverflow.com/questions/40780004/returning-notimplemented-from-eq

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!