Python中的__eq__是如何处理的,顺序是什么?

153 投票
3 回答
188132 浏览
提问于 2025-04-16 03:24

因为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 个回答

53

Python 3 对这个算法的变化/更新

在 Python 中,__eq__ 是怎么处理的,顺序是什么?

a == b

通常来说,a == b 会调用 a.__eq__(b),或者 type(a).__eq__(a, b),不过这并不总是如此。

具体来说,执行的顺序是:

  1. 如果 b 的类型是 a 类型的严格子类(不是同一种类型),并且有 __eq__ 方法,就调用它并返回比较的结果(如果实现了),
  2. 否则,如果 a__eq__ 方法,就调用它并返回结果(如果实现了),
  3. 否则,看看我们是否没有调用 b__eq__ 方法,如果有,就调用它并返回结果(如果实现了),
  4. 最后,如果都没有,就进行身份比较,也就是和 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.valueother,而不是 other.value - 在这个比较中,我们有两个对象(selfother),通常是同一种类型,因为我们没有进行类型检查(但它们可以是不同的类型),我们需要知道它们是否相等。我们判断它们是否相等的标准是检查 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;

总结

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

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

最后,我们使用身份测试来进行相等性比较。

74

当 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),只有当 ab 是同一个对象时,结果才是 True

如果任何一个特殊方法返回 NotImplemented,Python 会认为这个方法不存在。

请注意最后一步:如果 ab 都没有重载 ==,那么 a == b 就等同于 a is b


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

168

表达式 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 ?

撰写回答