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

时间:2016-09-18 13:54:21

标签: python python-2.7 operators

不要混淆苹果和橘子

问题

我正在使用__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) - &gt; False

1 个答案:

答案 0 :(得分:7)

这是Python跟踪器中的issue #6970;它在2.7和Python 3.0和3.1中保持不固定。

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

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

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

最后,这意味着在apple.__eq__(apple, orange)中首次尝试调用orange.__eq__(orange, apple)try_rich_compare(),然后调用反向,从而生成orange.__eq__(orange, apple)和{ {1}} apple.__eq__(apple, orange)self之间的other调用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->tp_richcompare(v, w),然后可以跳过强制(对于数字)。但是,当该比较失败(返回NotImplemented)时,标准路径尝试。

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