由于Python不提供其比较运算符的左/右版本,它如何决定调用哪个函数?
class A(object):
def __eq__(self, other):
print "A __eq__ called"
return self.value == other
class B(object):
def __eq__(self, other):
print "B __eq__ called"
return self.value == other
>>> a = A()
>>> a.value = 3
>>> b = B()
>>> b.value = 4
>>> a == b
"A __eq__ called"
"B __eq__ called"
False
这似乎同时调用__eq__
个函数。只是寻找官方决策树。
答案 0 :(得分:86)
a == b
表达式调用A.__eq__
,因为它存在。其代码包括self.value == other
。由于int不知道如何将自己与B进行比较,因此Python会尝试调用B.__eq__
来查看它是否知道如何将自身与int进行比较。
如果您修改代码以显示正在比较的值:
class A(object):
def __eq__(self, other):
print("A __eq__ called: %r == %r ?" % (self, other))
return self.value == other
class B(object):
def __eq__(self, other):
print("B __eq__ called: %r == %r ?" % (self, other))
return self.value == other
a = A()
a.value = 3
b = B()
b.value = 4
a == b
它会打印出来:
A __eq__ called: <__main__.A object at 0x013BA070> == <__main__.B object at 0x013BA090> ?
B __eq__ called: <__main__.B object at 0x013BA090> == 3 ?
答案 1 :(得分:54)
当Python2.x看到a == b
时,会尝试以下操作。
type(b)
是新式课程,且type(b)
是type(a)
的子类,且type(b)
已覆盖__eq__
,则结果为b.__eq__(a)
。type(a)
已覆盖__eq__
(即type(a).__eq__
不是object.__eq__
),则结果为a.__eq__(b)
。type(b)
已覆盖__eq__
,则结果为b.__eq__(a)
。__cmp__
的过程。如果存在,则对象是相等的,如果它返回zero
。object.__eq__(a, b)
,True
iff a
和b
是同一个对象。如果任何特殊方法返回NotImplemented
,则Python就像该方法不存在一样。
请注意最后一步:如果a
和b
都不重载==
,则a == b
与a is b
相同。
答案 2 :(得分:12)
我正在为Python 3写一个更新的答案。
如何用Python处理
__eq__
并以什么顺序进行操作?a == b
通常理解,但并非总是如此,a == b
调用a.__eq__(b)
或type(a).__eq__(a, b)
。
明确地,评估顺序为:
b
的类型是a
类型的严格子类(不是同一类型)并且具有__eq__
,则调用它,如果比较是,则返回值已实施a
有__eq__
,则调用它并在实现比较后返回它,__eq__
并拥有它,如果实现了比较,则调用并返回它,is
相同。我们知道,如果该方法返回NotImplemented
,是否未实现比较。
(在Python 2中,有一个__cmp__
方法正在寻找,但在Python 3中已弃用并删除了该方法。)
让我们通过让B子类A来测试自己的第一次检查行为,这表明接受的答案在这一点上是错误的:
class A:
value = 3
def __eq__(self, other):
print('A __eq__ called')
return self.value == other.value
class B(A):
value = 4
def __eq__(self, other):
print('B __eq__ called')
return self.value == other.value
a, b = A(), B()
a == b
仅在返回B __eq__ called
之前打印False
。
这里的其他答案似乎不完整且过时,因此我将更新信息 并向您展示如何自行查找。
这是在C级别处理的。
我们需要在这里查看两个不同的代码位-类__eq__
的对象的默认object
以及查找并调用__eq__
方法的代码,无论是否它使用默认的__eq__
或自定义的。
__eq__
在relevant C api docs中查找__eq__
会向我们显示__eq__
由tp_richcompare
处理-在cpython/Objects/typeobject.c
的"object"
类型定义中是在object_richcompare
中为case Py_EQ:
定义的。
case Py_EQ:
/* Return NotImplemented instead of False, so if two
objects are compared, both get a chance at the
comparison. See issue #1393. */
res = (self == other) ? Py_True : Py_NotImplemented;
Py_INCREF(res);
break;
因此,在这里,如果self == other
返回True
,否则我们返回NotImplemented
对象。这是未实现自己的__eq__
方法的任何对象子类的默认行为。
__eq__
的调用方式然后我们找到C API文档PyObject_RichCompare函数,它调用do_richcompare
。
然后我们看到为tp_richcompare
C定义创建的"object"
函数被do_richcompare
调用,因此让我们更仔细地研究一下。
此功能的第一次检查是针对要比较的对象的条件:
__eq__
方法,然后在交换参数的情况下调用对方的方法,如果实现则返回值。如果未实现该方法,我们继续...
if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
(f = Py_TYPE(w)->tp_richcompare) != NULL) {
checked_reverse_op = 1;
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
接下来,我们看看是否可以从第一种类型中查找__eq__
方法并进行调用。
只要结果不是NotImplemented,即已实现,我们就将其返回。
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
否则,如果我们不尝试其他类型的方法而该方法在那里,那么我们将尝试它,如果实现了比较,则将其返回。
if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
最后,如果未针对某人的类型实施此操作,我们会退一步。
回退检查对象的身份,即对象是否在内存中的同一位置–与self is other
的检查相同:
/* If neither object implements it, provide a sensible default
for == and !=, but raise an exception for ordering. */
switch (op) {
case Py_EQ:
res = (v == w) ? Py_True : Py_False;
break;
在比较中,我们首先尊重比较的子类实现。
然后,我们尝试与第一个对象的实现进行比较,如果未调用第二个对象,则与第二个对象进行比较。
最后,我们使用身份测试来比较是否相等。