问题:__eq__如何在Python中以什么顺序处理?

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

我正在寻找官方决策树。

Since Python does not provide left/right versions of its comparison operators, how does it decide which function to call?

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

This seems to call both __eq__ functions.

I am looking for the official decision tree.


回答 0

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 ?

The a == b expression invokes A.__eq__, since it exists. Its code includes self.value == other. Since int’s don’t know how to compare themselves to B’s, Python tries invoking B.__eq__ to see if it knows how to compare itself to an int.

If you amend your code to show what values are being compared:

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

it will print:

A __eq__ called: <__main__.A object at 0x013BA070> == <__main__.B object at 0x013BA090> ?
B __eq__ called: <__main__.B object at 0x013BA090> == 3 ?

回答 1

当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/

When Python2.x sees a == b, it tries the following.

  • If type(b) is a new-style class, and type(b) is a subclass of type(a), and type(b) has overridden __eq__, then the result is b.__eq__(a).
  • If type(a) has overridden __eq__ (that is, type(a).__eq__ isn’t object.__eq__), then the result is a.__eq__(b).
  • If type(b) has overridden __eq__, then the result is b.__eq__(a).
  • If none of the above are the case, Python repeats the process looking for __cmp__. If it exists, the objects are equal iff it returns zero.
  • As a final fallback, Python calls object.__eq__(a, b), which is True iff a and b are the same object.

If any of the special methods return NotImplemented, Python acts as though the method didn’t exist.

Note that last step carefully: if neither a nor b overloads ==, then a == b is the same as a is b.


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


回答 2

我正在为这个问题写一个更新的Python 3答案。

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

a == b

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

明确地,评估顺序为:

  1. 如果b的类型是的类型的严格子类(不是同一类型),a并且具有__eq__,则调用它并在实现比较后返回值,
  2. 否则,如果ahas __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

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

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

这是在C级别处理的。

我们需要在这里查看两个不同的代码位-class __eq__对象的默认值object,以及查找和调用该__eq__方法的代码,而不管它是使用默认值__eq__还是使用自定义代码。

默认 __eq__

__eq__相关的C api文档中查找显示了__eq__tp_richcompare– 处理的,该"object"类型cpython/Objects/typeobject.cobject_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

然后,我们看到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);

接下来,我们看是否可以__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;

结论

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

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

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

I’m writing an updated answer for Python 3 to this question.

How is __eq__ handled in Python and in what order?

a == b

It is generally understood, but not always the case, that a == b invokes a.__eq__(b), or type(a).__eq__(a, b).

Explicitly, the order of evaluation is:

  1. if b‘s type is a strict subclass (not the same type) of a‘s type and has an __eq__, call it and return the value if the comparison is implemented,
  2. else, if a has __eq__, call it and return it if the comparison is implemented,
  3. else, see if we didn’t call b’s __eq__ and it has it, then call and return it if the comparison is implemented,
  4. else, finally, do the comparison for identity, the same comparison as is.

We know if a comparison isn’t implemented if the method returns NotImplemented.

(In Python 2, there was a __cmp__ method that was looked for, but it was deprecated and removed in Python 3.)

Let’s test the first check’s behavior for ourselves by letting B subclass A, which shows that the accepted answer is wrong on this count:

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

which only prints B __eq__ called before returning False.

How do we know this full algorithm?

The other answers here seem incomplete and out of date, so I’m going to update the information and show you how how you could look this up for yourself.

This is handled at the C level.

We need to look at two different bits of code here – the default __eq__ for objects of class object, and the code that looks up and calls the __eq__ method regardless of whether it uses the default __eq__ or a custom one.

Default __eq__

Looking __eq__ up in the relevant C api docs shows us that __eq__ is handled by tp_richcompare – which in the "object" type definition in cpython/Objects/typeobject.c is defined in object_richcompare for 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;

So here, if self == other we return True, else we return the NotImplemented object. This is the default behavior for any subclass of object that does not implement its own __eq__ method.

How __eq__ gets called

Then we find the C API docs, the PyObject_RichCompare function, which calls do_richcompare.

Then we see that the tp_richcompare function, created for the "object" C definition is called by do_richcompare, so let’s look at that a little more closely.

The first check in this function is for the conditions the objects being compared:

  • are not the same type, but
  • the second’s type is a subclass of the first’s type, and
  • the second’s type has an __eq__ method,

then call the other’s method with the arguments swapped, returning the value if implemented. If that method isn’t implemented, we continue…

    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);

Next we see if we can lookup the __eq__ method from the first type and call it. As long as the result is not NotImplemented, that is, it is implemented, we return it.

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

Else if we didn’t try the other type’s method and it’s there, we then try it, and if the comparison is implemented, we return it.

    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);
    }

Finally, we get a fallback in case it isn’t implemented for either one’s type.

The fallback checks for the identity of the object, that is, whether it is the same object at the same place in memory – this is the same check as 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;

Conclusion

In a comparison, we respect the subclass implementation of comparison first.

Then we attempt the comparison with the first object’s implementation, then with the second’s if it wasn’t called.

Finally we use a test for identity for comparison for equality.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。