对于没有覆盖的子类,python的a == b调用b .__ eq __(a)

sto*_*tic 19 python python-2.7

在python 2.7.6中,假设我有一个定义__eq__的类及其子类:

>>> class A(object):
...     def __eq__(self,other):
...         print self.__class__,other.__class__
...         return True
... 
>>> class B(A):
...     pass
... 
Run Code Online (Sandbox Code Playgroud)

现在我创建每个类的对象,并想要比较它们:

>>> a = A()
>>> b = B()
>>> a==b
Run Code Online (Sandbox Code Playgroud)

结果我得到:

<class '__main__.B'> <class '__main__.A'>
Run Code Online (Sandbox Code Playgroud)

这表明解释器正在调用b.__eq__(a),而不是a.__eq__(b)预期的.

文档的状态(强调):

  • 对于对象xy,首先x.__op__(y)是尝试.如果没有实现或返回NotImplemented,y.__rop__(x)则尝试.如果这也没有实现或返回NotImplemented,TypeError则引发异常.但请参阅以下异常:

  • 前一项的异常:如果左操作数是内置类型或新样式类的实例,并且右操作数是该类型或类的正确子类的实例并覆盖基__rop__()方法,则右操作数的__rop__()方法在左操作数的__op__()方法之前尝试.

    这样做是为了使子类可以完全覆盖二元运算符.否则,左操作数的__op__()方法将始终接受右操作数:当期望给定类的实例时,该类的子类的实例始终是可接受的.

由于子类B不会覆盖__eq__运算符,不a.__eq__(b) 应该调用而不是b.__eq__(a)这是预期的行为,还是一个错误?当我读到它时,它与文档相反:我是在误读文档还是遗漏了其他内容?

一些相关问题:

  • 这个答案引用了我上面引用的文档.在这种情况下,最后一个问题涉及内置类型(1)的对象和新样式类的实例之间的比较.在这里,我特意将父类的实例与子类的实例进行比较,该实例不会覆盖其父类的rop()方法(在这种情况下,__eq__两者都是op()rop()).

    在这种情况下,python实际上会调用b.__eq__(a)而不是a.__eq__(b)第一个,即使类B没有显式覆盖A.

Bre*_*arn 13

看起来子类被认为是"覆盖"超类行为,即使它所做的一切都是继承超类行为.在这种__eq__情况下很难看到,因为__eq__它是自己的反射,但如果使用不同的运算符,例如__lt____gt__,这是彼此的反射,你可以更清楚地看到它:

class A(object):
    def __gt__(self,other):
        print "GT", self.__class__, other.__class__

    def __lt__(self,other):
        print "LT", self.__class__, other.__class__

class B(A):
    pass
Run Code Online (Sandbox Code Playgroud)

然后:

>>> A() > B()
LT <class '__main__.B'> <class '__main__.A'>
Run Code Online (Sandbox Code Playgroud)

注意A.__gt__没有被调用; 相反,B.__lt__被称为.

Python 3中的文档是说明性的,因为它在规定在技术上更准确的(强调)不同的单词的规则:

如果右操作数的类型是左操作数类型的子类,并且该子类提供了操作的反射方法,则此方法将在左操作数的非反射方法之前调用.此行为允许子类覆盖其祖先的操作.

子类确实"提供"了反射方法,它只是通过继承提供它.如果你实际上删除了子类中反射的方法行为(通过返回NotImplemented),则正确调用超类方法(在子类1之后):

class A(object):
    def __gt__(self,other):
        print "GT", self.__class__, other.__class__

    def __lt__(self,other):
        print "LT", self.__class__, other.__class__

class B(A):
    def __lt__(self, other):
        print "LT", self.__class__, other.__class__
        return NotImplemented

>>> A() > B()
LT <class '__main__.B'> <class '__main__.A'>
GT <class '__main__.A'> <class '__main__.B'>
Run Code Online (Sandbox Code Playgroud)

所以基本上这似乎是一个文档错误.应该说,无论子类是否显式覆盖超类实现,总是先尝试子类反射方法(对于比较运算符).(正如Mark Dickinson在评论中指出的那样,它只对比较运算符起作用,而不是像__add__/ 这样的数学运算符对__radd__.)

实际上,这不太重要,因为你唯一一次注意到子类不会覆盖超类的时候.但是在这种情况下,子类行为的定义与超类的相同,所以调用哪一个并不重要(除非你做了一些危险的事情,比如在比较方法中改变对象,在这种情况下无论如何你应该保持警惕).

  • 但是对于比较运算符而言,行为仍然不同于常规算术运算符:如果`A`定义了`__add__`和`__radd__`,那么`A()+ B()`仍然首先调用`A`的方法.因此,您对"提供"(通过继承)的解释不适用于该情况. (2认同)

dan*_*ano 7

这是实现所描述逻辑的代码:

Python 2.7:

/* Macro to get the tp_richcompare field of a type if defined */
#define RICHCOMPARE(t) (PyType_HasFeature((t), Py_TPFLAGS_HAVE_RICHCOMPARE) \
             ? (t)->tp_richcompare : NULL)

...

static PyObject *
try_rich_compare(PyObject *v, PyObject *w, int op)
{
    richcmpfunc f;
    PyObject *res;

    if (v->ob_type != w->ob_type &&
        PyType_IsSubtype(w->ob_type, v->ob_type) &&
        (f = RICHCOMPARE(w->ob_type)) != NULL) {
        res = (*f)(w, v, _Py_SwappedOp[op]);  // We're executing this
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    if ((f = RICHCOMPARE(v->ob_type)) != NULL) {
        res = (*f)(v, w, op);  // Instead of this.
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    if ((f = RICHCOMPARE(w->ob_type)) != NULL) {
        return (*f)(w, v, _Py_SwappedOp[op]);
    }
    res = Py_NotImplemented;
    Py_INCREF(res);
    return res;
}
Run Code Online (Sandbox Code Playgroud)

Python 3.x:

/* Perform a rich comparison, raising TypeError when the requested comparison
   operator is not supported. */
static PyObject *
do_richcompare(PyObject *v, PyObject *w, int op)
{
    richcmpfunc f;
    PyObject *res;
    int checked_reverse_op = 0; 

    if (v->ob_type != w->ob_type &&
        PyType_IsSubtype(w->ob_type, v->ob_type) &&
        (f = w->ob_type->tp_richcompare) != NULL) {
        checked_reverse_op = 1; 
        res = (*f)(w, v, _Py_SwappedOp[op]);  // We're executing this
        if (res != Py_NotImplemented)
            return res; 
        Py_DECREF(res);
    }    
    if ((f = v->ob_type->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);   // Instead of this.
        if (res != Py_NotImplemented)
            return res; 
        Py_DECREF(res);
    }    
    if (!checked_reverse_op && (f = w->ob_type->tp_richcompare) != NULL) {
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res; 
        Py_DECREF(res);
    }    
Run Code Online (Sandbox Code Playgroud)

这两个版本是相似的,只是Python 2.7版本使用RICHCOMPARE宏来检查PyType_HasFeature((t), Py_TPFLAGS_HAVE_RICHCOMPARE而不是ob_type->tp_richcompare != NULL.

在这两个版本中,第一个if块的评估结果为true.根据文档中的描述,人们可能期望是假的特定部分是:( f = w->ob_type->tp_richcompare != NULL对于Py3)/ PyType_HasFeature((t), Py_TPFLAGS_HAVE_RICHCOMPARE.但是,文档说这tp_richcompare是由子类继承的:

richcmpfunc PyTypeObject.tp_richcompare

指向丰富比较函数的可选指针......

该字段由子类型以及tp_compare和tp_hash继承...

随着2.x版,PyType_HasFeature((t), Py_TPFLAGS_HAVE_RICHCOMPARE也将评估为真,因为Py_TPFLAGS_HAVE_RICHCOMPARE 如果标志为true tp_richcompare,tp_cleartp_traverse是真实的,所有这些都继承自父.

因此,即使B不提供自己的丰富比较方法,它仍然返回非NULL值,因为它的父类提供它.正如其他人所说,这似乎是一个文档错误; 子类实际上不需要覆盖__eq__父类的方法,它只需要提供一个,甚至通过继承.