Python中`x==y`何时会调用`y.__eq__(x)`?
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的回答和评论,似乎可以得出以下几点:
- 丰富比较会覆盖
__cmp__
__eq__
是它自己的__rop__
,对应于它的__op__
(__lt__
、__ge__
等也是类似)- 如果左边的对象是内置类型或新式类,而右边是它的子类,则会先尝试右边对象的
__rop__
,再尝试左边对象的__op__
这解释了TestStrCmp
示例中的行为。TestStrCmp
是str
的子类,但没有实现自己的__eq__
,所以在这两种情况下都优先调用str
的__eq__
(也就是说,tsc == "b"
会调用b.__eq__(tsc)
作为__rop__
,因为规则1)。
在TestStrEq
示例中,tse.__eq__
在两种情况下都被调用,因为TestStrEq
是str
的子类,所以优先调用它。
在TestEq
示例中,TestEq
实现了__eq__
而int
没有,所以__eq__
在两次调用中都被调用(规则1)。
但我仍然不明白第一个示例TestCmp
。tc
不是int
的子类,所以据我所知,1.__cmp__(tc)
应该被调用,但实际上并没有。
4 个回答
这个内容在语言参考中没有说明吗?我简单看了一下,发现如果定义了__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" == tsc
和tsc == "b"
这两种情况感到疑惑,对吗?无论哪种情况,都是在调用str.__eq__(onething, otherthing)
。因为你没有在TestStrCmp中重写__eq__
方法,最终你只是依赖于基础字符串方法,而它表示这两个对象不相等。
在不知道str.__eq__
的具体实现细节的情况下,我不确定("b").__eq__(tsc)
是否会返回NotImplemented
,从而给tsc一个机会来处理相等性测试。但即使它返回了,按照你定义的TestStrCmp,你仍然会得到一个错误的结果。
所以不太清楚你看到的情况是什么让你感到意外。
也许发生的情况是,如果在被比较的任一对象上定义了__eq__
,Python会优先使用__eq__
而不是__cmp__
,而你原本期待的是左边的对象上的__cmp__
会优先于右边对象上的__eq__
。是这个意思吗?
其实,在文档中提到:
[
__cmp__
会在比较操作时被调用,如果没有定义丰富的比较方法(见上文)。
__eq__
是一个丰富的比较方法,而在TestCmp
的情况下,它并没有被定义,所以就会调用__cmp__
。
你可能忽略了一个关键的例外情况:当右边的操作数是左边操作数类的子类实例时,右边操作数的特殊方法会优先被调用。
可以查看文档了解更多信息:
http://docs.python.org/reference/datamodel.html#coercion-rules
特别是以下两段内容:
对于对象
x
和y
,首先会尝试x.__op__(y)
。如果这个方法没有实现或者返回NotImplemented
,那么就会尝试y.__rop__(x)
。如果这个方法也没有实现或者返回NotImplemented
,那么就会抛出一个类型错误(TypeError)。但是请注意以下例外情况:对于之前提到的情况,如果左边的操作数是一个内置类型或新式类的实例,而右边的操作数是该类型或类的一个合适的子类实例,并且重写了基类的
__rop__()
方法,那么会优先尝试右边操作数的__rop__()
方法,而不是左边操作数的__op__()
方法。