在Python中,__eq__是如何处理的?处理顺序是什么?

inkz8wg9  于 2023-01-16  发布在  Python
关注(0)|答案(4)|浏览(194)

既然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__函数。
我在找官方的决策树。

jaxagkaj

jaxagkaj1#

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 ?
dfty9e19

dfty9e192#

当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__的过程,如果__cmp__存在,则两个对象相等当且仅当它返回zero
  • 作为最后一个后备,Python调用object.__eq__(a, b),它是True当且仅当ab是同一个对象。

如果任何一个特殊的方法返回NotImplemented,Python就会认为这个方法不存在。
请仔细注意最后一步:如果ab都没有过载==,则a == ba is b相同。
https://eev.ee/blog/2012/03/24/python-faq-equality/开始

igetnqfo

igetnqfo3#

此算法的Python 3更改/更新

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

a == b

通常理解,但不总是这样,a == b调用a.__eq__(b)type(a).__eq__(a, b)
明确地说,评估的顺序是:
1.如果X1 M4 N1 X的类型是X1 M5 N1 X的类型的严格子类(不是相同类型)并且具有X1 M6 N1 X,则调用它并且在比较被实现的情况下返回值,
1.否则,如果a__eq__,则调用并返回比较结果,
1.否则,看看我们是否没有调用B的__eq__,它有,如果比较成功就调用返回,
1.否则,最后进行同一性比较,与is进行相同的比较。
如果方法返回NotImplemented,我们就知道是否没有实现比较。
(In 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__方法的代码,不管它使用默认__eq__还是自定义的__eq__

默认值__eq__

relevant C api docs中查找__eq__,我们发现__eq__tp_richcompare处理-在cpython/Objects/typeobject.c"object"类型定义中,tp_richcompare是在object_richcompare中为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__方法的object子类的默认行为。

如何调用__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;

结论

在比较中,我们首先考虑比较的子类实现。
然后,我们尝试与第一个对象的实现进行比较,如果没有调用,则与第二个对象的实现进行比较。
最后,我们使用同一性检验来比较是否相等。

brccelvz

brccelvz4#

在Python中,==操作符调用操作符左边的对象的eq方法,如果那个对象没有eq方法,Python会检查操作符右边的对象是否有eq方法,然后调用它。
在您提供的示例中,a对象有一个eq方法,b对象有一个eq方法。首先调用a对象的eq方法,然后调用b对象的eq方法。首先调用左侧对象的方法,然后调用右侧对象的方法(如果第一个方法不存在)。
如果您想定义not equal to运算符的工作方式,也可以在类中定义ne方法。

class A:

  def __eq__(self, other):
    print "A __eq__ called"
    return self.value == other

  def __ne__(self, other):
    return self.value != other

这样,Python将使用ne方法来判断left和right对象是否不相等,而不是使用eq方法并对结果求反。
一般来说,Python的运算符重载方法被设计成对称的,这意味着如果你覆盖了一个类中的eq方法,那么如果你想比较两个类的对象,你也应该覆盖另一个类中的eq方法。

相关问题