【问题标题】: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__ 函数。

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

【问题讨论】:

    标签: python comparison user-defined


    【解决方案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),但并非总是如此。

    明确地,评估的顺序是:

    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
    

    在返回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 不像你的帖子。
    • 这是我默认纠正的提问者的另一个错误,但我现在已经明确说明我在做什么......
    猜你喜欢
    • 1970-01-01
    • 2014-08-04
    • 2016-04-17
    • 1970-01-01
    • 2021-07-24
    • 1970-01-01
    • 1970-01-01
    • 2011-07-06
    相关资源
    最近更新 更多