此算法的 Python 3 更改/更新
__eq__ 在 Python 中是如何处理的以及按什么顺序处理?
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__ 方法的代码,无论它是否使用默认@987654351 @ 或自定义的。
默认__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__ 方法的对象子类的默认行为。
__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的检查相同:
/* 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;
结论
在比较中,我们首先尊重比较的子类实现。
然后我们尝试与第一个对象的实现进行比较,如果没有调用,则与第二个对象进行比较。
最后,我们使用身份测试来比较相等性。