在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州(强调补充):
对于对象
x
和y
,首先尝试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
。
答案 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)
这是实现所述逻辑的代码:
/* 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;
}
/* 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_richcompare
,Py_TPFLAGS_HAVE_RICHCOMPARE
和tp_clear
tp_traverse
标记为真是的,所有这些都是从父母那里继承的。
因此,即使B
没有提供自己的丰富比较方法,它仍会返回非NULL值,因为它的父类提供它。正如其他人所说,这似乎是一个文档错误;子类实际上并不需要覆盖父类的__eq__
方法,它只需提供一个,即使是通过继承。