在Python中,为什么/何时`x==y`会调用`y.__eq__(x)`?

37
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__(类似于__lt____ge__等等)
  3. 如果左侧对象是内置或新式类,并且右侧对象是它的子类,则在调用左侧对象的__op__之前,将尝试右侧对象的__rop__

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

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

TestEq示例中,TestEq实现了__eq__int没有,因此__eq__被连续调用两次(规则1)。

但是我仍然不理解TestCmp的第一个示例。 tc不是int的子类,因此根据我的理解应该调用1.__cmp__(tc),但没有。

4个回答

33

您缺少一个关键的例外,来说明通常行为的情况:当右操作数是左操作数类的子类的实例时,将首先调用右操作数的特殊方法。

请参阅文档:

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

特别是以下两段:

对于对象xy,首先尝试执行x.__op__(y)。如果未实现或返回NotImplemented,则尝试执行y.__rop__(x)。如果这也未实现或返回NotImplemented,则会引发TypeError异常。但请参见下面的异常:

前一项的例外情况:如果左操作数是内置类型或新式类的实例,并且右操作数是该类型或类的适当子类的实例并覆盖了基类的__rop__()方法,则在尝试执行左操作数的__op__()方法之前,将尝试执行右操作数的__rop__()方法。


4
同意您没有使用任何__rop__方法。比较方法在这方面是特殊的:__eq__是其自身的反向方法,因此对于__op____rop__都应该阅读__eq__。(类似地,__ne__是其自身的反向方法,__le____ge__的反向方法等等。)其他人之前也评论过(我认为正确),文档在这方面需要进行一些完善。我几乎可以确定__rop__方法并未被弃用! - Mark Dickinson
3
请参阅http://docs.python.org/reference/datamodel.html#object.__lt__。第四段开始说:“这些方法没有交换参数的版本[...] 相反, __lt__()__gt__() 互为镜像,__le__()__ge__() 互为镜像,而 __eq__()__ne__() 则是它们自己的镜像。”或者你是在询问 __ror__ 方法不会被弃用的证据? - Mark Dickinson
你说得对,那肯定需要澄清。我已经多次阅读了那个页面,但还没有理解它所说的“__lt__”是“__gt__”的“__rop__”。另外,这不是错误的吗?__lt__不应该是__ge__的__rop__吗?例如(2<2)==(not(2>=2)) - Singletoned
2
不,我认为这是正确的:__lt____gt____rop__。这里没有进行逻辑否定;只是对参数进行了反转。翻译如下:x.__lt__(y) <=> x < y <=> y > x <=> y.__gt__(x) - Mark Dickinson
2
__cmp__(尤其是与丰富比较结合的情况)的规则非常复杂,我认为它们甚至没有被适当地记录在任何地方,除了源代码。如果你感兴趣的话,(可以)在 Objects/object.c 文件中的 PyObject_RichCompare 中查找。在 1 == tc 示例中,两侧都没有实现 __eq__,因此我们回退到 __cmp__int.__cmp__ 只能处理与其他整数的比较,所以它会被忽略(甚至不会被调用),而是调用你的 TestCmp.__cmp__ 方法。在 py3k 中消除 __cmp__ 是一件非常好的事情。 :) - Mark Dickinson
显示剩余5条评论

6
实际上,在文档中,它指出:
“如果未定义富比较(见上文),则比较操作将调用__cmp__方法。” __eq__是一个富比较方法,在TestCmp的情况下没有定义,因此调用了__cmp__

但是 str.__eq__ 已被定义,因此可以假定 TestStrCmp.__eq__ 也已被定义(继承)。 - dubiousjim
1
你说得没错,__eq__确实会覆盖__cmp__,但这并不是令人惊讶的行为。令人惊讶的是它在右侧对象上调用而不是左侧对象。(我已经更新了问题以澄清这一点)。 - Singletoned

1

这个在语言参考中没有记录吗?仅仅从快速查看的结果来看,当定义了__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的方式仍然会得到错误的结果。

因此,不清楚你在这里看到了什么意外的东西。

也许发生的情况是,如果在任何一个被比较的对象上定义了__eq____cmp__,Python会更喜欢使用__eq__,而你可能期望最左边的对象上的__cmp__优先于右边对象上的__eq__。是这样吗?

经过进一步的尝试,我认为您是正确的:在这些情况下,无论哪个对象上都更倾向于使用__eq__ - Singletoned

1
据我所知,__eq__()是一种所谓的“丰富比较”方法,并且在比下面的__cmp__()之前调用比较运算符。如果未定义“丰富比较”,则会调用__cmp__()
因此,在A == B中:
如果A中定义了__eq__(),则会调用它
否则将调用__cmp__() __eq__()在'str'中定义,因此未调用您的__cmp__()函数。
对于__ne__(), __gt__(), __ge__(), __lt__()__le__()“丰富比较”方法也适用同样的规则。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接