128

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

我正在寻找官方的决策树。

4

3 回答 3

152

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 ?
于 2010-08-27T23:45:53.143 回答
72

当 Python2.x 看到a == b时,它会尝试以下操作。

  • 如果type(b)是新式类,并且type(b)是 的子类type(a),并且type(b)已经被覆盖__eq__,那么结果是b.__eq__(a)
  • 如果type(a)已覆盖__eq__(即type(a).__eq__is not object.__eq__),则结果为a.__eq__(b).
  • 如果type(b)已覆盖__eq__,则结果为b.__eq__(a)
  • 如果以上都不是,Python 会重复这个过程来寻找__cmp__. 如果它存在,则当它返回时,对象是相等的zero
  • 作为最后的后备,Python 调用iff并且object.__eq__(a, b)是同一个对象。Trueab

如果任何特殊方法返回NotImplemented,Python 就好像该方法不存在一样。

请注意最后一步:如果既不a也不b重载==,则a == b与 相同a is b


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

于 2012-10-20T03:28:45.423 回答
31

此算法的 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

请注意,我还更正了问题中的一个小错误,即与 whereself.value进行比较other而不是other.value- 在此比较中,我们得到两个对象 ( selfand other),通常是相同类型的,因为我们在这里没有进行类型检查(但它们可以是不同的类型),我们需要知道它们是否相等。我们衡量它们是否相等的方法是检查value属性,这必须在两个对象上完成。

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

此处的其他答案似乎不完整且已过时,因此我将更新信息并向您展示如何自己查找。

这是在 C 级别处理的。

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

默认__eq__

__eq__相关的 C api 文档中查找向我们展示了__eq__tp_richcompare- 在"object"类型定义中cpython/Objects/typeobject.c定义的object_richcomparefor 处理的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.

然后我们看到为C 定义tp_richcompare创建的函数是由 调用的,所以让我们更仔细地看一下。"object"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;

结论

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

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

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

于 2020-08-07T23:23:25.257 回答