【问题标题】:Why/When in Python does `x==y` call `y.__eq__(x)`?为什么/何时在 Python 中 `x==y` 调用 `y.__eq__(x)`?
【发布时间】:2011-01-17 21:10:40
【问题描述】:

Python 文档明确指出x==y 调用x.__eq__(y)。然而,在许多情况下,情况似乎恰恰相反。它在哪里记录了发生这种情况的时间或原因,以及如何确定我的对象的 __cmp____eq__ 方法是否会被调用。

编辑:澄清一下,我知道__eq__ 被优先调用而不是__cmp__,但我不清楚为什么y.__eq__(x) 被优先调用而不是x.__eq__(y),而后者是文档中的内容状态会发生。

>>> class TestCmp(object):
...     def __cmp__(self, other):
...         print "__cmp__ got called"
...         return 0
... 
>>> class TestEq(object):
...     def __eq__(self, other):
...         print "__eq__ got called"
...         return True
... 
>>> tc = TestCmp()
>>> te = TestEq()
>>> 
>>> 1 == tc
__cmp__ got called
True
>>> tc == 1
__cmp__ got called
True
>>> 
>>> 1 == te
__eq__ got called
True
>>> te == 1
__eq__ got called
True
>>> 
>>> class TestStrCmp(str):
...     def __new__(cls, value):
...         return str.__new__(cls, value)
...     
...     def __cmp__(self, other):
...         print "__cmp__ got called"
...         return 0
... 
>>> class TestStrEq(str):
...     def __new__(cls, value):
...         return str.__new__(cls, value)
...     
...     def __eq__(self, other):
...         print "__eq__ got called"
...         return True
... 
>>> tsc = TestStrCmp("a")
>>> tse = TestStrEq("a")
>>> 
>>> "b" == tsc
False
>>> tsc == "b"
False
>>> 
>>> "b" == tse
__eq__ got called
True
>>> tse == "b"
__eq__ got called
True

编辑:从 Mark Dickinson 的回答和评论看来:

  1. 丰富的比较覆盖__cmp__
  2. __eq__ 是它自己的 __rop__ 还是 __op____lt____ge__ 等类似)
  3. 如果左侧对象是内置或新样式类,而右侧是它的子类,则在左侧对象的 __op__ 之前尝试右侧对象的 __rop__

这解释了TestStrCmp 示例中的行为。 TestStrCmpstr 的子类,但没有实现它自己的 __eq__ 所以 __eq__str 在这两种情况下都优先(即 tsc == "b" 调用 b.__eq__(tsc) 作为 __rop__ 因为规则 1)。

TestStrEq 示例中,tse.__eq__ 在两个实例中都被调用,因为TestStrEqstr 的子类,因此它被优先调用。

TestEq 示例中,TestEq 实现了 __eq__int 没有,所以 __eq__ 被调用两次(规则 1)。

但我仍然不明白TestCmp 的第一个例子。 tc 不是 int 的子类,因此应该调用 AFAICT 1.__cmp__(tc),但不是。

【问题讨论】:

    标签: python comparison operator-overloading


    【解决方案1】:

    您错过了通常行为的关键异常:当右侧操作数是左侧操作数类的子类的实例时,首先调用右侧操作数的特殊方法。

    查看文档:

    http://docs.python.org/reference/datamodel.html#coercion-rules

    特别是以下两段:

    对于对象xy,首先 x.__op__(y) 已尝试。如果这不是 实施或返回 NotImplemented, y.__rop__(x) 是 试过了。如果这也没有实施 或返回NotImplemented,一个 引发 TypeError 异常。但见 以下例外:

    上一项的例外:如果 左操作数是 a 的一个实例 内置类型或新型类, 右边的操作数是一个实例 该类型的适当子类或 类并覆盖基类的 __rop__()方法,对 尝试操作数的__rop__() 方法 在左操作数的 __op__() 之前 方法。

    【讨论】:

    • @Daniel Pryden:感谢格式修复!下次我会尽量记住blockquote。
    • 同意您没有使用任何__rop__ 方法。比较方法在这方面比较特殊:__eq__ 是它自己的反面,所以对于__op____rop__,请阅读__eq__。 (同样,__ne__ 是它自己的反向,__le____ge__ 的反向,等等。)其他人之前(正确地,IMO)评论说文档可以在这里使用一些工作。我几乎可以肯定 __rop__ 方法没有被弃用!
    • docs.python.org/reference/datamodel.html#object.__lt__。第四段开始:“这些方法没有交换参数版本 [...] 相反,__lt__()__gt__() 是彼此的反映,__le__()__ge__() 是彼此的反映,@987654345 @ 和 __ne__() 是他们自己的反映。”或者您是否要求提供 __rop__ 方法未被弃用的证据?
    • 不,我认为是对的:__lt____gt____rop__。没有逻辑上的否定;只是对论点的颠倒。翻译为:x.__lt__(y) x < y y > x y.__gt__(x).
    • __cmp__ 的规则(尤其是结合丰富的比较)确实很麻烦,而且我认为它们甚至没有在任何地方正确记录,除了源代码。 (Objects/object.c 文件中的 PyObject_RichCompare 是主要查看的地方,如果您愿意的话。)在1 == tc 示例中,双方都没有实现__eq__,所以我们求助于__cmp__int.__cmp__ 只能处理与其他整数的比较,因此它被忽略(甚至不调用),而是调用您的 TestCmp.__cmp__ 方法。在 py3k 中摆脱 __cmp__ 是一件非常好的事情。 :)
    【解决方案2】:

    这不是在Language Reference 中记录的吗?快速浏览一下,在定义 __eq____lt__ 等时,似乎会忽略 __cmp__。我理解这包括在父类上定义__eq__ 的情况。 str.__eq__ 已定义,因此其子类上的 __cmp__ 将被忽略。 object.__eq__ 等未定义,因此其子类上的 __cmp__ 将得到尊重。

    针对已澄清的问题:

    我知道__eq__ 被调用 更喜欢__cmp__,但我不是 清楚为什么调用y.__eq__(x) 偏好x.__eq__(y),当 后者是文档状态的内容 发生。

    文档说x.__eq__(y) 将首先被调用,但它可以选择返回NotImplemented,在这种情况下y.__eq__(x) 被调用。我不知道你为什么确信这里发生了一些不同的事情。

    您对哪种情况特别感到困惑?我理解您只是对"b" == tsctsc == "b" 案件感到困惑,对吗?无论哪种情况,都会调用str.__eq__(onething, otherthing)。由于您没有覆盖 TestStrCmp 中的 __eq__ 方法,因此最终您只是依赖于基本字符串方法,它表示对象不相等。

    在不知道str.__eq__的实现细节的情况下,不知道("b").__eq__(tsc)是否会返回NotImplemented并给tsc一个机会来处理相等测试。但即使这样做了,按照您定义 TestStrCmp 的方式,您仍然会得到错误的结果。

    所以不清楚你在这里看到了什么是意外的。

    也许正在发生的事情是 Python 更喜欢 __eq__ 而不是 __cmp__,如果它是在 任何一个被比较的对象上定义的,而您期望最左边的对象上的 __cmp__ 具有优先权在右侧对象上的__eq__ 上。是这样吗?

    【讨论】:

    • 玩得更久了,我认为你是对的,在这些情况下,__eq__ 在任一对象上都是首选。
    【解决方案3】:

    实际上,在docs 中,它声明:

    [__cmp__]如果未定义丰富的比较(见上文),则由比较操作调用。

    __eq__ 是一种丰富的比较方法,对于TestCmp,没有定义,因此调用__cmp__

    【讨论】:

    • 但是str.__eq__被定义了,所以推测TestStrCmp.__eq__被定义(继承)。
    • __eq__ 覆盖 __cmp__ 是对的,但这并不是令人惊讶的行为。令人惊讶的是,它在右侧对象而不是左侧对象上调用它。 (我已经更新了问题以澄清这一点)。
    【解决方案4】:

    据我所知,__eq__() 是一种所谓的“富比较”方法,下面的比较运算符优先于__cmp__() 调用。 __cmp__() 如果未定义“丰富的比较”,则调用。

    所以在 A == B:
    如果在 A 中定义了__eq__(),它将被调用
    否则将调用__cmp__()

    __eq__() 在 'str' 中定义,因此您的 __cmp__() 函数未被调用。

    __ne__(), __gt__(), __ge__(), __lt__()__le__() “丰富的比较”方法也有相同的规则。

    【讨论】:

      猜你喜欢
      • 2020-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多