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


96

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

我正在寻找官方决策树。

Answers:


119

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 ?

69

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


1
嗯,似乎python 3文档不正确。请参阅bugs.python.org/issue4395和该修补程序以进行澄清。TLDR:子类仍然比较优先,即使它在rhs上也是如此。
最高

嗨,凯夫,好帖子。您能解释第一个要点记录在哪里以及为什么要这样设计吗?
2015年

1
是的,这在python 2中有记载吗?是PEP吗?
Mr_and_Mrs_D

基于这个答案和附带的评论,这让我比以前更加困惑。
Sajuuk

和顺便说一句,是__eq__ 仅在某种类型的实例上定义一个绑定方法,不足以使==被覆盖吗?
Sajuuk

3

我正在为这个问题写一个更新的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;

结论

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.