这个算法的Python 3变化/更新
How is __eq__
handled in Python and in what order?
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
只会在返回False
之前打印出B __eq__ called
。请注意,我还更正了问题中的一个小错误,即将self.value
与other
相比较,而不是与other.value
相比较 - 在此比较中,我们得到两个对象(self
和other
),通常是相同类型的,因为我们在这里没有进行类型检查(但它们可以是不同的类型),我们需要知道它们是否相等。我们衡量它们是否相等的标准是检查value
属性,必须在两个对象上都执行。
我们如何知道这个完整的算法?
这里的其他答案似乎不完整并且过时,所以我要更新信息并向您展示如何查找此信息。
这是在C级别处理的。
我们需要查看这里的两个不同的代码片段 - 类object
的默认__eq__
和查找并调用__eq__
方法的代码,无论它使用默认的__eq__
还是自定义的__eq__
。
默认__eq__
在relevant C api docs中查找__eq__
会显示出__eq__
是由tp_richcompare
处理的 - 在cpython/Objects/typeobject.c
中的"object"
类型定义中,对于case Py_EQ:
,它在object_richcompare
中定义。
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__
方法的
object
子类的默认行为。
如何调用__eq__
然后我们找到C API文档,
PyObject_RichCompare函数,它调用
do_richcompare
。
然后我们看到
"object"
C定义创建的
tp_richcompare
函数由
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
的检查相同:
switch (op) {
case Py_EQ:
res = (v == w) ? Py_True : Py_False;
break;
结论
在比较中,我们首先尊重比较的子类实现。
然后,如果第一个对象的实现没有被调用,我们尝试使用第一个对象的实现进行比较,然后再尝试使用第二个对象的实现。
最后,我们使用身份测试来比较相等性。