在 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__函数。

我在找正式的决策树。

176159 次浏览

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 ?

当 Python 2.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)
  • 如果上述情况都不存在,Python 将重复寻找 __cmp__的过程。如果它存在,对象是相等的,如果它返回 zero
  • 最后,Python 调用 object.__eq__(a, b),即 True,当 ab是同一个对象时。

如果任何一个特殊的方法返回 NotImplemented,Python 的行为就好像这个方法不存在一样。

注意最后一步: 如果 ab都不重载 ==,那么 a == ba is b是一样的。


来自 https://eev.ee/blog/2012/03/24/python-faq-equality/

Python 3此算法的更改/更新

如何在 Python 中处理 __eq__以及按什么顺序?

a == b

人们通常理解 a == b调用 a.__eq__(b)type(a).__eq__(a, b),但并非总是如此。

显然,评估的顺序是:

  1. 如果 b的类型是 a类型的严格子类(不是同一类型) ,并且有一个 __eq__,那么在实现比较时调用它并返回值,
  2. 否则,如果 a__eq__,则调用它并在实现比较时返回它,
  3. Else,看看我们是否调用了 b 的 __eq__并且它已经有了,然后调用并返回它,如果比较已经实现,
  4. 最后,进行身份的比较,与 is相同。

我们知道如果方法返回 NotImplemented,比较是否没有实现。

(在 Python2中,我们查找了一个 __cmp__方法,但是在 Python3中它被弃用和删除了。)

让我们通过让 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.valueother而不是 other.value进行比较-在这个比较中,我们得到了两个对象(selfother) ,通常是同一种类型,因为我们在这里没有做类型检查(但它们可以是不同的类型) ,我们需要知道它们是否相等。我们衡量它们是否相等的方法是检查 value属性,这必须在两个对象上执行。

我们怎么知道这个完整的算法?

这里的其他答案似乎不完整和过时,所以我要更新的信息 还有告诉你如何可以查找自己。

这是在 C 级处理的。

这里我们需要查看两段不同的代码——类 object对象的默认 __eq__,以及查找和调用 __eq__方法的代码,而不管它使用的是默认 __eq__还是自定义 __eq__

默认 __eq__

相关的 Capi 文件中向上查看 __eq__向我们展示了 __eq__是由 tp_richcompare处理的——在 cpython/Objects/typeobject.c中的 "object"类型定义中,object_richcomparecase Py_EQ:定义了 tp_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;

这里,如果我们返回 True,或者返回 NotImplemented对象。这是任何没有实现自己的 __eq__方法的对象子类的默认行为。

__eq__是如何被称为的

然后我们找到 CAPI 文档,即调用 do_richcomparePyObject _ 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__方法并调用它。 只要结果不是 Not實现的,也就是说,它是实现的,我们就返回它。

    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;

结论

在比较中,我们首先尊重比较的子类实现。

然后我们尝试与第一个对象的实现进行比较,如果没有调用,则与第二个对象的实现进行比较。

最后,我们使用一个等同性测试来进行等同性比较。