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

PyP*_*rog 76 python comparison user-defined

由于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
Run Code Online (Sandbox Code Playgroud)

这似乎称为两种__eq__功能.只是寻找官方的决策树.

Ned*_*der 97

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
Run Code Online (Sandbox Code Playgroud)

它将打印:

A __eq__ called: <__main__.A object at 0x013BA070> == <__main__.B object at 0x013BA090> ?
B __eq__ called: <__main__.B object at 0x013BA090> == 3 ?
Run Code Online (Sandbox Code Playgroud)

  • 绝对正确.总而言之,这些测试应该是"return self.value == other.value". (13认同)

kev*_*kev 56

当Python2.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),它是Trueiff a并且b是同一个对象.

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

请注意最后一步:如果既不是a也不b重载==,那么a == b就是相同的a is b.


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

  • python 3中的顺序是什么?并记录了python 2的订单在哪里? (6认同)

Aar*_*all 21

此算法的 Python 3 更改/更新

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

a == b
Run Code Online (Sandbox Code Playgroud)

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

明确地,评估的顺序是:

  1. 如果b的类型是 的类型的严格子类(不是同一类型)a并且具有__eq__,则调用它并在实现比较时返回值,
  2. 否则,如果a__eq__,则调用它并在实现比较时返回它,
  3. 否则,看看我们是否没有调用 b's__eq__并且它有它,然后如果实现了比较则调用并返回它,
  4. 否则,最后,做身份的比较,与 相同的比较is

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

(在 Python 2 中,有一个__cmp__被寻找的方法,但它在 Python 3 中被弃用并删除了。)

让我们通过让 B 子类化 A 来为自己测试第一个检查的行为,这表明接受的答案在这个计数上是错误的:

a == b
Run Code Online (Sandbox Code Playgroud)

B __eq__ called在返回之前打印False

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

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

这是在 C 级别处理的。

我们需要在这里查看两个不同的代码位 - __eq__class 对象的默认值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;
Run Code Online (Sandbox Code Playgroud)

所以在这里,如果self == other我们返回True,否则我们返回NotImplemented对象。这是任何未实现其自身__eq__方法的对象子类的默认行为。

如何__eq__被调用

然后我们找到 C API 文档,即PyObject_RichCompare函数,它调用do_richcompare.

然后我们看到tp_richcompare"object"C 定义创建的函数被调用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);
Run Code Online (Sandbox Code Playgroud)

接下来我们看看是否可以__eq__从第一种类型中查找方法并调用它。只要结果不是NotImplemented,即实现了,我们就返回它。

    if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
Run Code Online (Sandbox Code Playgroud)

否则如果我们没有尝试其他类型的方法并且它在那里,我们然后尝试它,如果实现了比较,我们返回它。

    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);
    }
Run Code Online (Sandbox Code Playgroud)

最后,我们得到一个回退,以防它没有为任何一个类型实现。

回退检查对象的身份,即它是否是内存中同一位置的同一对象 - 这与 for 的检查相同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;
Run Code Online (Sandbox Code Playgroud)

结论

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

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

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

  • 非常详细的答案。只是为了记录,这里是[文档](https://docs.python.org/3/reference/datamodel.html#object.__eq__)的相关部分: *如果操作数的类型不同,并且右操作数的类型是左操作数类型的直接或间接子类,则右操作数的反射方法优先,否则左操作数的方法优先。不考虑虚拟子类化。* (2认同)