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

时间:2010-08-27 23:34:19

标签: python comparison user-defined

由于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__个函数。只是寻找官方决策树。

3 个答案:

答案 0 :(得分:86)

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 ?

答案 1 :(得分:54)

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

答案 2 :(得分:12)

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

如何用Python处理__eq__并以什么顺序进行操作?

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

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

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

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

这是在C级别处理的。

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

默认__eq__

relevant C api docs中查找__eq__会向我们显示__eq__tp_richcompare处理-在cpython/Objects/typeobject.c"object"类型定义中是在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__方法的任何对象子类的默认行为。

__eq__的调用方式

然后我们找到C API文档PyObject_RichCompare函数,它调用do_richcompare

然后我们看到为tp_richcompare C定义创建的"object"函数被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;

结论

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

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

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