不要混淆苹果和橙子
问题
我正在使用__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
。 最佳答案
这是Python跟踪器中的issue #6970;在2.7,Python 3.0和3.1中仍未修复。
这是由于在执行两个使用__eq__
方法的自定义类之间的比较时,在两个位置尝试直接比较和交换比较引起的。
丰富的比较通过 PyObject_RichCompare()
function进行,它针对具有不同类型的对象(间接地)委托(delegate)给 try_rich_compare()
。在此函数中,v
和w
是左操作数对象和右操作数对象,并且由于两者都具有__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)
。
最后,这意味着第一次尝试在apple.__eq__(apple, orange)
中调用orange.__eq__(orange, apple)
和try_rich_compare()
,然后调用相反的方法,导致orange.__eq__(orange, apple)
和apple.__eq__(apple, orange)
被self
和other
交换后被调用。
请注意,此问题仅限于不同自定义类的实例,其中两个类都定义了slot_tp_richcompare()
方法。如果任何一方都没有这样的方法,__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__
返回__eq__
,那么您甚至可以得到六个比较:>>> 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
为了进行优化,我们调用了第一组两个比较。当两个实例具有相同的类型时,您只需要调用
NotImplemented
即可,毕竟可以跳过强制(对于数字)。但是,当比较失败(返回v->tp_richcompare(v, w)
)时,也会尝试使用标准路径。在Python 2中如何进行比较变得相当复杂,因为仍然必须支持较旧的
NotImplemented
3向比较方法。在Python 3中,由于不再支持__cmp__
,因此更容易解决该问题。因此,此修复程序从未回退到2.7。