__eq__ 在 Python 中是如何处理的以及按啥顺序处理?

Posted

技术标签:

【中文标题】__eq__ 在 Python 中是如何处理的以及按啥顺序处理?【英文标题】:How is __eq__ handled in Python and in what order?__eq__ 在 Python 中是如何处理的以及按什么顺序处理? 【发布时间】:2011-04-05 01:32:45 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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 ?

【讨论】:

对于那些想知道的人来说也与 Python3 相关。【参考方案2】:

当 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),即 True iff ab 是同一个对象。

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

注意最后一步:如果ab 都没有重载==,那么a == ba is b 相同。


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

【讨论】:

呃,python 3 文档似乎不正确。请参阅bugs.python.org/issue4395 和补丁以进行澄清。 TLDR:子类仍然首先比较,即使它在 rhs 上。 嗨,kev,好帖子。您能否解释一下第一个要点记录在哪里以及为什么要这样设计? 是的,这在哪里记录了 python 2 ?是 PEP 吗? 基于这个答案并伴随着 cmets ,这让我比以前更加困惑。 顺便说一句,是在某些类型的实例上定义绑定方法__eq__ 不足以覆盖 == 吗?【参考方案3】:

此算法的 Python 3 更改/更新

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

a == b

通常情况下,a == b 调用 a.__eq__(b)type(a).__eq__(a, b),但并非总是如此。

明确地,评估的顺序是:

    如果b的类型是a的类型的严格子类(不是同一类型)并且有__eq__,则调用它并返回值,如果实现了比较, 否则,如果a__eq__,则调用它并在实现比较时返回它, 否则,看看我们是不是没有调用b的__eq__,它有,如果实现了比较,则调用并返回, 否则,最后,进行身份比较,与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

在返回False之前只打印B __eq__ called

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

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

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

这是在 C 级别处理的。

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

默认__eq__

在relevant C api docs 中查找__eq__ 向我们展示了__eq__tp_richcompare 处理-在cpython/Objects/typeobject.c 中的"object" 类型定义中为case Py_EQ: 定义了object_richcompare

    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

然后我们看到为"object" C 定义创建的tp_richcompare 函数被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 年为 Python3 提供更新。也许值得一提的是,数学运算符对左/右有不同的处理程序,并且都得到了尝试,例如a=A(), a+1 调用 a.__add__ 并且 1+a 尝试 A.__radd__ 如果 int add 无法处理 A。关于如何捕获失败的 1+a 并导致 A.__radd__ 的任何想法? “在这个计数上接受的答案是错误的” 但是他们有self.value == other 不像你的帖子。 这是我默认纠正的提问者的另一个错误,但我现在已经明确说明我在做什么......

以上是关于__eq__ 在 Python 中是如何处理的以及按啥顺序处理?的主要内容,如果未能解决你的问题,请参考以下文章

lua中是 ffi 解析 是如何处理数据包的/pkt是如何传进去的

lua中是 ffi 解析 是如何处理数据包的/pkt是如何传进去的 fsfsfs

tf.app.run() 是如何工作的?

Python面向对象编程第12篇 特殊方法之__eq__

Python面向对象编程第12篇 特殊方法之__eq__

为啥 Python 文档说我在定义 __eq__ 时需要定义 __ne__?