首页 文章

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

提问于
浏览
11

不要混淆苹果和橘子

问题

我正在玩 __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 .

1 回答

  • 7

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

    这是由 two places 在执行带有 __eq__ 方法的两个自定义类之间的比较时尝试直接比较和交换比较引起的 .

    丰富的比较通过PyObject_RichCompare() function,对于具有不同类型(间接)委托的对象,try_rich_compare() . 在此函数中, vw 是左右操作数对象,由于两者都有 __eq__ 方法,因此该函数同时调用 v->ob_type->tp_richcompare()w->ob_type->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) 调用 selfotherslot_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 ,你甚至可以获得 six 比较:

    >>> 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__ 三向比较方法;在Python 3中,由于支持 __cmp__ 已删除,因此更容易解决问题 . 因此,修复程序从未向后移植到2.7 .

相关问题