⼀、问题的发现与提出
在Python类的⽅法(method)中,要调⽤⽗类的某个⽅法,在Python 2.2以前,通常的写法如代码段1:代码段1:复制代码 代码如下: class A:
def __init__(self): print \"enter A\" print \"leave A\" class B(A):
def __init__(self): print \"enter B\" A.__init__(self) print \"leave B\" >>> b = B() enter B enter A leave A leave B
即,使⽤⾮绑定的类⽅法(⽤类名来引⽤的⽅法),并在参数列表中,引⼊待绑定的对象(self),从⽽达到调⽤⽗类的⽬的。
这样做的缺点是,当⼀个⼦类的⽗类发⽣变化时(如类B的⽗类由A变为C时),必须遍历整个类定义,把所有的通过⾮绑定的⽅法的类名全部替换过来,例如代码段2, 代码段2:复制代码 代码如下: class B(C): # A --> C def __init__(self): print \"enter B\"
C.__init__(self) # A --> C print \"leave B\"
如果代码简单,这样的改动或许还可以接受。但如果代码量庞⼤,这样的修改可能是灾难性的。
因此,⾃Python 2.2开始,Python添加了⼀个关键字super,来解决这个问题。下⾯是Python 2.3的官⽅⽂档说明:复制代码 代码如下: super(type[, object-or-type])
Return the superclass of type. If the second argument is omitted the super object returned is unbound. If the second argument is an object, isinstance(obj, type) must be true. If the second argument is a type, issubclass(type2, type) must be true. super() only works for new-style classes.
A typical use for calling a cooperative superclass method is: class C(B):
def meth(self, arg):
super(C, self).meth(arg) New in version 2.2.
从说明来看,可以把类B改写如代码段3: 代码段3:复制代码 代码如下:
class A(object): # A must be new-style class def __init__(self): print \"enter A\" print \"leave A\"
class B(C): # A --> C def __init__(self): print \"enter B\"
super(B, self).__init__() print \"leave B\"
尝试执⾏上⾯同样的代码,结果⼀致,但修改的代码只有⼀处,把代码的维护量降到最低,是⼀个不错的⽤法。因此在我们的开发过程中,super关键字被⼤量使⽤,⽽且⼀直表现良好。
在我们的印象中,对于super(B, self).__init__()是这样理解的:super(B, self)⾸先找到B的⽗类(就是类A),然后把类B的对象self转换为类A的对象(通过某种⽅式,⼀直没有考究是什么⽅式,惭愧),然后“被转换”的类A对象调⽤⾃⼰的
__init__函数。考虑到super中只有指明⼦类的机制,因此,在多继承的类定义中,通常我们保留使⽤类似代码段1的⽅法。 有⼀天某同事设计了⼀个相对复杂的类体系结构(我们先不要管这个类体系设计得是否合理,仅把这个例⼦作为⼀个题⽬来研究就好),代码如代码段4:代码段4:复制代码 代码如下: class A(object): def __init__(self): print \"enter A\" print \"leave A\" class B(object): def __init__(self): print \"enter B\" print \"leave B\"
class C(A):
def __init__(self): print \"enter C\"
super(C, self).__init__() print \"leave C\"
class D(A):
def __init__(self): print \"enter D\"
super(D, self).__init__() print \"leave D\" class E(B, C): def __init__(self): print \"enter E\" B.__init__(self) C.__init__(self) print \"leave E\" class F(E, D): def __init__(self): print \"enter F\" E.__init__(self) D.__init__(self)
print \"leave F\" >>> f = F() enter F enter E enter B leave B enter C enter D enter A leave A leave D leave C leave E enter D enter A leave A leave D leave F
明显地,类A和类D的初始化函数被重复调⽤了2次,这并不是我们所期望的结果!我们所期望的结果是最多只有类A的初始化函数被调⽤2次——其实这是多继承的类体系必须⾯对的问题。我们把代码段4的类体系画出来,如下图:复制代码 代码如下: object | / | A | / | B C D / / | E | / | F
按我们对super的理解,从图中可以看出,在调⽤类C的初始化函数时,应该是调⽤类A的初始化函数,但事实上却调⽤了类D的初始化函数。好⼀个诡异的问题!⼆、⾛进Python的源码世界
我们尝试改写代码段4中的函数调⽤,但都没有得到我们想要的结果,这不得不使我们开始怀疑:我们对super的理解是否出了问题。
我们重新阅读了Python的官⽅⽂档,正如您所见,官⽅⽂档并没有详细的原理说明。到⽹络上去搜索,确实有⼈发现了同样的问题,并在⼀些论坛中讨论,但似乎并没有实质性的解答。既然,没有前⼈的⾜迹,我们只好⾛进Python的源码世界,去追溯问题的根源。
我们考查的是Python 2.3的源码(估计Python 2.4的源码可能也差不多)。⾸先,搜索关键字\"super\"。唯⼀找到的是bltinmodule.c中的⼀句:复制代码 代码如下:
SETBUILTIN(\"super\
于是,我们有了对super的第⼀个误解:super并⾮是⼀个函数,⽽是⼀个类(PySuper_Type)。 在typeobject.c中找到了PySuper_Type的定义: 代码段5:复制代码 代码如下:
PyTypeObject PySuper_Type = {
PyObject_HEAD_INIT(&PyType_Type) 0, /* ob_size */
\"super\
sizeof(superobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */
super_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ super_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */
super_getattro, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ super_doc, /* tp_doc */
super_traverse, /* tp_traverse */ 0, /* tp_clear */
0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */
super_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */
super_descr_get, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ super_init, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */ PyType_GenericNew, /* tp_new */ PyObject_GC_Del, /* tp_free */ };
从代码段5中可以得知,super类只改写了⼏个⽅法,最主要的包括:tp_dealloc,tp_getattro,tp_traverse,tp_init。 再看superobject的定义: 代码段6:复制代码 代码如下: typedef struct { PyObject_HEAD PyTypeObject *type; PyObject *obj;
PyTypeObject *obj_type; } superobject;
从代码段6中可以看到superobject的数据成员仅有3个指针(3个对象的引⽤)。要知道这3个对象分别代表什么,则必需考查super_init的定义: 代码段7:
复制代码 代码如下:
static int
super_init(PyObject *self, PyObject *args, PyObject *kwds) {
superobject *su = (superobject *)self; PyTypeObject *type; PyObject *obj = NULL;
PyTypeObject *obj_type = NULL;
if (!PyArg_ParseTuple(args, \"O!|O:super\ return -1;
if (obj == Py_None) obj = NULL; if (obj != NULL) {
obj_type = supercheck(type, obj); if (obj_type == NULL) return -1;
Py_INCREF(obj); }
Py_INCREF(type); su->type = type; su->obj = obj;
su->obj_type = obj_type; return 0; }
从代码中可以看到,super_init⾸先通过PyArg_ParseTuple把传⼊的参数列表解释出来,分别放在type和obj变量之中。然后通过supercheck测试可选参数obj是否合法,并获得实例obj的具体类类型。最后,把type, obj和obj_type记录下来。也就是说,super对象只是简单作了⼀些记录,并没有作任何转换操作。
查找问题的切⼊点是为什么在类C中的super调⽤会切换到类D的初始化函数。于是在super_init中添加条件断点,并跟踪其后的Python代码。最终进⼊到super_getattro函数——对应于super对象访问名字__init__时的搜索操作。 代码段8(省略部分⽆关代码,并加⼊⼀些注释):复制代码 代码如下:
static PyObject *
super_getattro(PyObject *self, PyObject *name) {
superobject *su = (superobject *)self; int skip = su->obj_type == NULL; ……
if (!skip) {
PyObject *mro, *res, *tmp, *dict; PyTypeObject *starttype; descrgetfunc f; int i, n;
starttype = su->obj_type; // 获得搜索的起点:super对象的obj_type mro = starttype->tp_mro; // 获得类的mro ……
for (i = 0; i < n; i++) { // 搜索mro中,定位mro中的type
if ((PyObject *)(su->type) == PyTuple_GET_ITEM(mro, i)) break; }
i++; // 切换到mro中的下⼀个类 res = NULL;
for (; i < n; i++) { // 在mro以后的各个命名空间中搜索指定名字 tmp = PyTuple_GET_ITEM(mro, i); if (PyType_Check(tmp))
dict = ((PyTypeObject *)tmp)->tp_dict;
else if (PyClass_Check(tmp))
dict = ((PyClassObject *)tmp)->cl_dict; else
continue;
res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res);
f = res->ob_type->tp_descr_get; if (f != NULL) {
tmp = f(res, su->obj, (PyObject *)starttype); Py_DECREF(res); res = tmp; }
return res; } } }
return PyObject_GenericGetAttr(self, name); }
从代码中可以看出,super对象在搜索命名空间时,其实是基于类实例的mro进⾏。那么什么是mro呢?查找官⽅⽂档,有:
复制代码 代码如下:
PyObject* tp_mro
Tuple containing the expanded set of base types, starting with the type itself and ending with object, in Method Resolution Order.
This field is not inherited; it is calculated fresh by PyType_Ready().
也就是说,mro中记录了⼀个类的所有基类的类类型序列。查看mro的记录,发觉包含7个元素,7个类名分别为:复制代码 代码如下: F E B C D A object
从⽽说明了为什么在C.__init__中使⽤super(C, self).__init__()会调⽤类D的初始化函数了。 我们把代码段4改写为: 代码段9:复制代码 代码如下:
class A(object): def __init__(self): print \"enter A\"
super(A, self).__init__() # new print \"leave A\"
class B(object): def __init__(self): print \"enter B\"
super(B, self).__init__() # new print \"leave B\"
class C(A):
def __init__(self): print \"enter C\"
super(C, self).__init__() print \"leave C\"
class D(A):
def __init__(self): print \"enter D\"
super(D, self).__init__() print \"leave D\" class E(B, C): def __init__(self): print \"enter E\"
super(E, self).__init__() # change print \"leave E\"
class F(E, D): def __init__(self): print \"enter F\"
super(F, self).__init__() # change print \"leave F\" >>> f = F() enter F enter E enter B enter C enter D enter A leave A leave D leave C leave B leave E leave F
明显地,F的初始化不仅完成了所有的⽗类的调⽤,⽽且保证了每⼀个⽗类的初始化函数只调⽤⼀次。三、延续的讨论
我们再重新看上⾯的类体系图,如果把每⼀个类看作图的⼀个节点,每⼀个从⼦类到⽗类的直接继承关系看作⼀条有向边,那么该体系图将变为⼀个有向图。不能发现mro的顺序正好是该有向图的⼀个拓扑排序序列。
从⽽,我们得到了另⼀个结果——Python是如何去处理多继承。⽀持多继承的传统的⾯向对象程序语⾔(如C++)是通过虚拟继承的⽅式去实现多继承中⽗类的构造函数被多次调⽤的问题,⽽Python则通过mro的⽅式去处理。
但这给我们⼀个难题:对于提供类体系的编写者来说,他不知道使⽤者会怎么使⽤他的类体系,也就是说,不正确的后续类,可能会导致原有类体系的错误,⽽且这样的错误⾮常隐蔽的,也难于发现。四、⼩结
1. super并不是⼀个函数,是⼀个类名,形如super(B, self)事实上调⽤了super类的初始化函数, 产⽣了⼀个super对象;
2. super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例; 3. super(B, self).func的调⽤并不是⽤于调⽤当前类的⽗类的func函数;
4. Python的多继承类是通过mro的⽅式来保证各个⽗类的函数被逐⼀调⽤,⽽且保证每个⽗类函数 只调⽤⼀次(如果每个类都使⽤super);
5. 混⽤super类和⾮绑定的函数是⼀个危险⾏为,这可能导致应该调⽤的⽗类函数没有调⽤或者⼀ 个⽗类函数被调⽤多次。
因篇幅问题不能全部显示,请点此查看更多更全内容