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

时间:2014-07-23 19:34:30

标签: 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
... 

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

>>> a = A()
>>> b = B()
>>> a==b

我得到的结果:

<class '__main__.B'> <class '__main__.A'>

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

documentation州(强调补充):

  
      
  • 对于对象xy,首先尝试x.__op__(y)。如果未执行此操作或返回NotImplemented,则会尝试y.__rop__(x)。如果这也未实现或返回NotImplemented,则会引发TypeError异常。但请参阅以下例外:

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

         

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

  •   

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

一些相关问题:

  • This answer引用我上面引用的文档。在这种情况下,最后一个问题涉及内置类型(1)的对象和实例之间的比较 一个新的风格类。在这里,我特意比较了父类的实例 使用覆盖其 rop() 方法的子类实例 家长(在这种情况下,__eq__同时为 op() rop()

    在这种情况下,python实际上首先调用b.__eq__(a)而不是a.__eq__(b),即使类B未明确覆盖A

2 个答案:

答案 0 :(得分: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

然后:

>>> A() > B()
LT <class '__main__.B'> <class '__main__.A'>

请注意,A.__gt__未被调用;相反,B.__lt__被召唤。

Python 3 documentation是说明性的,因为它以不同的词语表明规则在技术上更准确(强调添加):

  

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

子类确实“提供”了反射方法,它只是通过继承提供它。如果你实际上删除了子类中反射的方法行为(通过返回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'>

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

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

答案 1 :(得分: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;
}

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);
    }    

这两个版本是相似的,只是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 is inherited by child classes

  

richcmpfunc PyTypeObject.tp_richcompare

     

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

     

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

使用2.x版本时,PyType_HasFeature((t), Py_TPFLAGS_HAVE_RICHCOMPARE也会评估为true,因为如果tp_richcomparePy_TPFLAGS_HAVE_RICHCOMPAREtp_clear tp_traverse标记为真是的,所有这些都是从父母那里继承的。

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