Python中`x==y`何时会调用`y.__eq__(x)`?

37 投票
4 回答
11783 浏览
提问于 2025-04-15 19:23

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. 如果左边的对象是内置类型或新式类,而右边是它的子类,则会先尝试右边对象的__rop__,再尝试左边对象的__op__

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

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

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

但我仍然不明白第一个示例TestCmptc不是int的子类,所以据我所知,1.__cmp__(tc)应该被调用,但实际上并没有。

4 个回答

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__,Python会优先使用__eq__而不是__cmp__,而你原本期待的是左边的对象上的__cmp__会优先于右边对象上的__eq__。是这个意思吗?

6

其实,在文档中提到:

[__cmp__会在比较操作时被调用,如果没有定义丰富的比较方法(见上文)。

__eq__是一个丰富的比较方法,而在TestCmp的情况下,它并没有被定义,所以就会调用__cmp__

33

你可能忽略了一个关键的例外情况:当右边的操作数是左边操作数类的子类实例时,右边操作数的特殊方法会优先被调用。

可以查看文档了解更多信息:

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

特别是以下两段内容:

对于对象 xy,首先会尝试 x.__op__(y)。如果这个方法没有实现或者返回 NotImplemented,那么就会尝试 y.__rop__(x)。如果这个方法也没有实现或者返回 NotImplemented,那么就会抛出一个类型错误(TypeError)。但是请注意以下例外情况:

对于之前提到的情况,如果左边的操作数是一个内置类型或新式类的实例,而右边的操作数是该类型或类的一个合适的子类实例,并且重写了基类的 __rop__() 方法,那么会优先尝试右边操作数的 __rop__() 方法,而不是左边操作数的 __op__() 方法。

撰写回答