Python中的__eq__是如何处理的,顺序是什么?
因为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__
函数。
我想找官方的决策流程图。
3 个回答
Python 3 对这个算法的变化/更新
在 Python 中,
__eq__
是怎么处理的,顺序是什么?a == b
通常来说,a == b
会调用 a.__eq__(b)
,或者 type(a).__eq__(a, b)
,不过这并不总是如此。
具体来说,执行的顺序是:
- 如果
b
的类型是a
类型的严格子类(不是同一种类型),并且有__eq__
方法,就调用它并返回比较的结果(如果实现了), - 否则,如果
a
有__eq__
方法,就调用它并返回结果(如果实现了), - 否则,看看我们是否没有调用
b
的__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
。
注意,我还纠正了问题中的一个小错误,原本是比较 self.value
和 other
,而不是 other.value
- 在这个比较中,我们有两个对象(self
和 other
),通常是同一种类型,因为我们没有进行类型检查(但它们可以是不同的类型),我们需要知道它们是否相等。我们判断它们是否相等的标准是检查 value
属性,这必须在两个对象上都进行。
我们怎么知道这个完整的算法?
这里的其他答案似乎不完整且过时,所以我将更新这些信息 并且 展示你如何自己查找这些内容。
这部分是在 C 语言层面处理的。
我们需要查看两段不同的代码 - 默认的 __eq__
方法对于 object
类的对象,以及查找和调用 __eq__
方法的代码,无论它是使用默认的 __eq__
还是自定义的。
默认的 __eq__
在 相关的 C API 文档 中查找 __eq__
,我们会发现它是由 tp_richcompare
处理的 - 在 "object"
类型定义中,cpython/Objects/typeobject.c
中定义了 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
函数是由 do_richcompare
调用的,所以我们来仔细看看。
这个函数的第一个检查是比较对象的条件:
- 不是同一种类型,但
- 第二个的类型是第一个类型的子类,并且
- 第二个的类型有
__eq__
方法,
然后调用另一个的 __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;
总结
在比较中,我们首先尊重子类的比较实现。
然后我们尝试用第一个对象的实现进行比较,如果没有调用,再用第二个的实现。
最后,我们使用身份测试来进行相等性比较。
当 Python2.x 看到 a == b
时,它会按照以下步骤进行处理。
- 如果
b
的类型是新式类,并且b
的类型是a
类型的子类,并且b
的类型重写了__eq__
方法,那么结果就是b.__eq__(a)
。 - 如果
a
的类型重写了__eq__
方法(也就是说,type(a).__eq__
不是object.__eq__
),那么结果就是a.__eq__(b)
。 - 如果
b
的类型重写了__eq__
方法,那么结果就是b.__eq__(a)
。 - 如果以上情况都不成立,Python 会继续查找
__cmp__
方法。如果这个方法存在,只有当它返回zero
时,两个对象才被认为是相等的。 - 最后,Python 会调用
object.__eq__(a, b)
,只有当a
和b
是同一个对象时,结果才是True
。
如果任何一个特殊方法返回 NotImplemented
,Python 会认为这个方法不存在。
请注意最后一步:如果 a
和 b
都没有重载 ==
,那么 a == b
就等同于 a is b
。
表达式 a == b
会调用 A.__eq__
,因为这个方法是存在的。这个方法的代码里有 self.value == other
。由于整数(int)不知道怎么和类B的对象比较,Python就会尝试调用 B.__eq__
,看看它是否知道怎么和整数比较。
如果你修改代码来显示正在比较的值:
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 ?