【问题标题】:Why is NotImplemented evaluated multiple times with __eq__ operator为什么使用 __eq__ 运算符多次评估 NotImplemented
【发布时间】:2017-01-26 06:28:51
【问题描述】:

不要把苹果和橙子混在一起

问题

我正在使用 __eq__ 运算符和 NotImplemented 值。

我试图了解当obj1.__eq__(obj2) 返回NotImplemented 并且obj2.__eq__(obj1) 也返回NotImplemented 时会发生什么。

根据Why return NotImplemented instead of raising NotImplementedError的回答,以及“LiveJournal”博客中的详细文章How to override comparison operators in Python,运行时应该回退到内置行为(基于 ==!= 的身份)。

代码示例

但是,尝试下面的示例,我似乎为每对对象多次调用 __eq__

class Apple(object):
    def __init__(self, color):
        self.color = color

    def __repr__(self):
        return "<Apple color='{color}'>".format(color=self.color)

    def __eq__(self, other):
        if isinstance(other, Apple):
            print("{self} == {other} -> OK".format(self=self, other=other))
            return self.color == other.color
        print("{self} == {other} -> NotImplemented".format(self=self, other=other))
        return NotImplemented


class Orange(object):
    def __init__(self, usage):
        self.usage = usage

    def __repr__(self):
        return "<Orange usage='{usage}'>".format(usage=self.usage)

    def __eq__(self, other):
        if isinstance(other, Orange):
            print("{self} == {other}".format(self=self, other=other))
            return self.usage == other.usage
        print("{self} == {other} -> NotImplemented".format(self=self, other=other))
        return NotImplemented

>>> apple = Apple("red")
>>> orange = Orange("juice")

>>> apple == orange
<Apple color='red'> == <Orange usage='juice'> -> NotImplemented
<Orange usage='juice'> == <Apple color='red'> -> NotImplemented
<Orange usage='juice'> == <Apple color='red'> -> NotImplemented
<Apple color='red'> == <Orange usage='juice'> -> NotImplemented
False

预期行为

我预计只有:

<Apple color='red'> == <Orange usage='juice'> -> NotImplemented
<Orange usage='juice'> == <Apple color='red'> -> NotImplemented

然后回到身份比较id(apple) == id(orange) -> False

【问题讨论】:

  • 我在 Python 2.7 OS X 上
  • 在 Windows 和 CentOS7 上与 Python 2.7 的行为相同。 Windows 上的 Python 3.5 和 repl.it 上的正常行为。

标签: python python-2.7 operators


【解决方案1】:

这是 Python 跟踪器中的issue #6970;它在 2.7 和 Python 3.0 和 3.1 中仍未修复。

这是由两个地方在使用__eq__ 方法执行两个自定义类之间的比较时尝试直接比较和交换比较造成的。

丰富的比较通过PyObject_RichCompare() function,对于不同类型的对象(间接)委托给try_rich_compare()。在此函数中,vw 是左右操作数对象,由于两者都有 __eq__ 方法,因此函数同时调用 v-&gt;ob_type-&gt;tp_richcompare()w-&gt;ob_type-&gt;tp_richcompare()

对于自定义类,tp_richcompare() slot 定义为slot_tp_richcompare() function,并且此函数再次对双方执行__eq__,首先是self.__eq__(self, other),然后是other.__eq__(other, self)

最后,这意味着在try_rich_compare()中第一次尝试调用apple.__eq__(apple, orange)orange.__eq__(orange, apple),然后调用反向,导致orange.__eq__(orange, apple)apple.__eq__(apple, orange)调用为selfother 被替换为 slot_tp_richcompare()

请注意,问题仅限于不同自定义类的实例,其中两个类都定义了__eq__ 方法。如果任何一方都没有这样的方法__eq__只执行一次:

>>> class Pear(object):
...     def __init__(self, purpose):
...         self.purpose = purpose
...     def __repr__(self):
...         return "<Pear purpose='{purpose}'>".format(purpose=self.purpose)
...    
>>> pear = Pear("cooking")
>>> apple == pear
<Apple color='red'> == <Pear purpose='cooking'> -> NotImplemented
False
>>> pear == apple
<Apple color='red'> == <Pear purpose='cooking'> -> NotImplemented
False

如果您有两个相同类型的实例并且__eq__ 返回NotImplemented,您甚至会得到六个比较:

>>> class Kumquat(object):
...     def __init__(self, variety):
...         self.variety = variety
...     def __repr__(self):
...         return "<Kumquat variety=='{variety}'>".format(variety=self.variety)
...     def __eq__(self, other):
...         # Kumquats are a weird fruit, they don't want to be compared with anything
...         print("{self} == {other} -> NotImplemented".format(self=self, other=other))
...         return NotImplemented
...
>>> Kumquat('round') == Kumquat('oval')
<Kumquat variety=='round'> == <Kumquat variety=='oval'> -> NotImplemented
<Kumquat variety=='oval'> == <Kumquat variety=='round'> -> NotImplemented
<Kumquat variety=='round'> == <Kumquat variety=='oval'> -> NotImplemented
<Kumquat variety=='oval'> == <Kumquat variety=='round'> -> NotImplemented
<Kumquat variety=='oval'> == <Kumquat variety=='round'> -> NotImplemented
<Kumquat variety=='round'> == <Kumquat variety=='oval'> -> NotImplemented
False

第一组两个比较是为了优化而调用的;当两个实例具有相同的类型时,您只需要调用v-&gt;tp_richcompare(v, w),毕竟可以跳过强制(对于数字)。但是,当比较失败(返回NotImplemented)时,尝试了标准路径。

如何在 Python 2 中进行比较变得相当复杂,因为仍然必须支持旧的 __cmp__ 3 路比较方法;在 Python 3 中,由于对 __cmp__ 的支持已被删除,因此更容易解决此问题。因此,该修复从未向后移植到 2.7。

【讨论】:

  • 伙计,我搜索了错误跟踪器,但找不到任何相关信息。它的源代码已经足够分散,以至于我放弃了弄清楚它在哪里进行双重后备检查(我发现定义__cmp____eq__ 会导致第三轮检查,但不是这个)。谢谢!
  • @ShadowRanger:我用 gdb 快速查看了示例(给我一个回溯,每次都指向slot_tp_richcompare),然后在跟踪器中搜索slot_tp_richcompare,发现了该报告。
猜你喜欢
  • 2014-06-17
  • 2011-10-11
  • 2017-04-08
  • 2014-12-15
  • 2019-05-27
  • 2016-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多