为什么使用__eq__运算符多次评估NotImplemented

Lau*_*RTE 11 python operators python-2.7

不要混淆苹果和橘子

问题

我正在玩__eq__操作员和NotImplemented价值.

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

根据为什么返回NotImplemented而不是引发NotImplementedError的答案,以及详细的文章如何在"LiveJournal"博客中覆盖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
Run Code Online (Sandbox Code Playgroud)

预期的行为

我希望只有:

<Apple color='red'> == <Orange usage='juice'> -> NotImplemented
<Orange usage='juice'> == <Apple color='red'> -> NotImplemented
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*ers 7

这是Python跟踪器中的问题#6970 ; 它在2.7和Python 3.0和3.1中保持不变.

这是由两个地方同时尝试直接和交换比较引起的,当__eq__执行两个自定义类与方法之间的比较时.

丰富的比较通过该PyObject_RichCompare()函数,对于具有不同类型(间接)委托的对象try_rich_compare().在这个函数中v,它w是左右操作数对象,并且由于两者都有一个__eq__方法,因此函数调用v->ob_type->tp_richcompare()w->ob_type->tp_richcompare().

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

最后,这意味着apple.__eq__(apple, orange)并且orange.__eq__(orange, apple)被调用第一次尝试try_rich_compare(),然后调用反向,导致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
Run Code Online (Sandbox Code Playgroud)

如果你有两个相同类型__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
Run Code Online (Sandbox Code Playgroud)

第一组两次比较是从优化尝试中调出来的; 当两个实例具有相同的类型时,您只需要调用v->tp_richcompare(v, w)并且可以跳过强制(对于数字).但是,当该比较失败(NotImplemented返回)时,也会尝试标准路径.

如何在Python 2中进行比较变得相当复杂,因为__cmp__仍需要支持旧的3向比较方法; 在Python 3中,支持__cmp__删除,更容易解决问题.因此,修复程序从未向后移植到2.7.