【问题标题】:Returning NotImplemented from __eq__从 __eq__ 返回 NotImplemented
【发布时间】:2017-04-08 09:07:08
【问题描述】:

在 python 3 中从__eq__ 特殊方法返回NotImplemented 的结果是什么(如果重要的话是3.5)?

文档不清楚; only relevant text I found 只是模糊地指“其他一些后备”:

NotImplemented 返回时,解释器将尝试其他类型的反射操作,或其他一些回退,具体取决于操作员。如果所有尝试的操作都返回NotImplemented,解释器将引发适当的异常。详情请见Implementing the arithmetic operations

不幸的是,“更多详细信息”链接根本没有提到__eq__

我对这段摘录的阅读表明,下面的代码应该引发“适当的异常”,但它不会:

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

通过实验,我认为当 NotImplemented__eq__ 返回时,解释器的行为就好像 __eq__ 没有首先定义(具体来说,它首先交换参数,如果没有t 解决问题,它使用默认的__eq__ 进行比较,如果两个对象具有相同的标识,则评估“相等”)。如果是这种情况,我可以在文档中的何处找到此行为的确认信息?

编辑:见Python issue 28785

【问题讨论】:

  • 要引发异常,您需要在代码中使用raise NotImplementedError
  • 在我的 Python 3 中,两者都评估为不相等。输入print("A.eq")print("A.eq") 看看会发生什么。首先称为“A.eq”,后来称为“B.eq”。后来可能在其他可以比较它并返回结果的数据类型中称为eq - 所以它不会引发错误。可能它比较 id(A())id(B())
  • id() 可以与每个对象一起使用,因此您不会出错。 __add__ 没有一些始终有效的通用方法,因此它可以引发异常。
  • stackoverflow.com/questions/878943/… - 第一个答案确认它使用身份。我不确定相关的解释器代码 sn-p 或官方文档链接。
  • @ŁukaszRogalski 是有道理的,但该链接是关于截至 2009 年的 python 2,并且基于博客文章而不是文档。它不应该在官方 python 3 文档中吗?也许我应该在 bugs.python.org 上提出问题?

标签: python python-3.x equality python-internals


【解决方案1】:

实际上,==!= 检查与排序比较运算符(< 和类似)的工作方式相同,只是它们不会引发适当的异常,而是回退到身份比较。这是唯一的区别。

这在CPython source code (version 3.5.10) 中很容易看到。我将包含该源代码的 Python 版本(至少尽可能):

_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

然后调用a == b 计算结果为richcmp(a, b, '__eq__')if op == '__eq__' 是使您的 a == b 返回 False (因为它们不是相同的对象)和您的 a == a 返回 True (因为它们是)的特殊情况。

然而,Python 2.x 中的行为完全不同。在回退到身份比较之前,您最多可以进行 4 次(甚至 6 次,我记不太清了)比较!

【讨论】:

    【解决方案2】:

    不确定它在文档中的位置(或是否),但基本行为是:

    • 试试操作:__eq__(lhs, rhs)
    • 如果结果不是NotImplemented,则返回
    • 其他尝试反射操作:__eq__(rhs, lhs)
    • 如果结果不是NotImplemented,则返回
    • 否则使用适当的后备:

      eq -> 相同的对象? -> 正确,否则错误

      ne -> 不同的对象?真,否则假

      许多其他人 -> 引发异常

    eqne引发异常的原因是:

    • 它们总是可以确定的(苹果 == 橙色?不)

    【讨论】:

    • 啊……好吧,当您在评论中谈到“相同的对象”时,我感到很困惑。 StackOverflow 上的新(粘性)标题阻止我看到您编辑了帖子。哎呀。
    • 这让我觉得这是一个非常糟糕的设计决定。没关系,它会导致难以捕获错误,但基本原理毫无意义:当实现确定两个对象不可比较时 (NotImplemented),为什么对象标识 every 会产生 True ?这根本不是这种情况,除非__eq__ 的实现在一开始就被破坏了。
    • @KonradRudolph:我不明白你关于“识别每个产量True”的评论——有没有漏掉一个词?
    • @Ethan 我的意思是“曾经”,而不是“每个”。
    • 我认为这里糟糕的设计决策是NotImplemented 概念本身(至少对于__eq____ne__):比较方法的作者应始终明确选择后备比较(如果任何);使用根据比较和对象对而变化的隐式后备是导致所有这些的原因。
    猜你喜欢
    • 2017-01-26
    • 1970-01-01
    • 2010-10-27
    • 2014-08-17
    • 2020-01-28
    • 1970-01-01
    • 1970-01-01
    • 2014-06-17
    相关资源
    最近更新 更多