一、問題的發(fā)現(xiàn)與提出
在Python類的方法(method)中,要調(diào)用父類的某個(gè)方法,在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
即,使用非綁定的類方法(用類名來引用的方法),并在參數(shù)列表中,引入待綁定的對(duì)象(self),從而達(dá)到調(diào)用父類的目的。
這樣做的缺點(diǎn)是,當(dāng)一個(gè)子類的父類發(fā)生變化時(shí)(如類B的父類由A變?yōu)镃時(shí)),必須遍歷整個(gè)類定義,把所有的通過非綁定的方法的類名全部替換過來,例如代碼段2,
?代碼段2:
?class B(C):??? # A --> C
? def __init__(self):
?? print "enter B"
?? C.__init__(self) # A --> C
?? print "leave B"
如果代碼簡(jiǎn)單,這樣的改動(dòng)或許還可以接受。但如果代碼量龐大,這樣的修改可能是災(zāi)難性的。
因此,自Python 2.2開始,Python添加了一個(gè)關(guān)鍵字super,來解決這個(gè)問題。下面是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"
嘗試執(zhí)行上面同樣的代碼,結(jié)果一致,但修改的代碼只有一處,把代碼的維護(hù)量降到最低,是一個(gè)不錯(cuò)的用法。因此在我們的開發(fā)過程中,super關(guān)鍵字被大量使用,而且一直表現(xiàn)良好。
在我們的印象中,對(duì)于super(B, self).__init__()是這樣理解的:super(B, self)首先找到B的父類(就是類A),然后把類B的對(duì)象self轉(zhuǎn)換為類A的對(duì)象(通過某種方式,一直沒有考究是什么方式,慚愧),然后“被轉(zhuǎn)換”的類A對(duì)象調(diào)用自己的__init__函數(shù)。考慮到super中只有指明子類的機(jī)制,因此,在多繼承的類定義中,通常我們保留使用類似代碼段1的方法。
有一天某同事設(shè)計(jì)了一個(gè)相對(duì)復(fù)雜的類體系結(jié)構(gòu)(我們先不要管這個(gè)類體系設(shè)計(jì)得是否合理,僅把這個(gè)例子作為一個(gè)題目來研究就好),代碼如代碼段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的初始化函數(shù)被重復(fù)調(diào)用了2次,這并不是我們所期望的結(jié)果!我們所期望的結(jié)果是最多只有類A的初始化函數(shù)被調(diào)用2次――其實(shí)這是多繼承的類體系必須面對(duì)的問題。我們把代碼段4的類體系畫出來,如下圖:
??? object
?? |?????? /
?? |??????? A
?? |????? / |
?? B? C? D
??? /?? /?? |
????? E??? |
??????? /?? |
????????? F
按我們對(duì)super的理解,從圖中可以看出,在調(diào)用類C的初始化函數(shù)時(shí),應(yīng)該是調(diào)用類A的初始化函數(shù),但事實(shí)上卻調(diào)用了類D的初始化函數(shù)。好一個(gè)詭異的問題!
二、走進(jìn)Python的源碼世界
我們嘗試改寫代碼段4中的函數(shù)調(diào)用,但都沒有得到我們想要的結(jié)果,這不得不使我們開始懷疑:我們對(duì)super的理解是否出了問題。
我們重新閱讀了Python的官方文檔,正如您所見,官方文檔并沒有詳細(xì)的原理說明。到網(wǎng)絡(luò)上去搜索,確實(shí)有人發(fā)現(xiàn)了同樣的問題,并在一些論壇中討論,但似乎并沒有實(shí)質(zhì)性的解答。既然,沒有前人的足跡,我們只好走進(jìn)Python的源碼世界,去追溯問題的根源。
我們考查的是Python 2.3的源碼(估計(jì)Python 2.4的源碼可能也差不多)。首先,搜索關(guān)鍵字"super"。唯一找到的是bltinmodule.c中的一句:
?SETBUILTIN("super",? &PySuper_Type);
于是,我們有了對(duì)super的第一個(gè)誤解:super并非是一個(gè)函數(shù),而是一個(gè)類(PySuper_Type)。
在typeobject.c中找到了PySuper_Type的定義:
?代碼段5:
?PyTypeObject PySuper_Type = {
? PyObject_HEAD_INIT(&PyType_Type)
? 0,???? /* ob_size */
? "super",??? /* tp_name */
? 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類只改寫了幾個(gè)方法,最主要的包括:tp_dealloc,tp_getattro,tp_traverse,tp_init。
再看superobject的定義:
?代碼段6:
?typedef struct {
? PyObject_HEAD
? PyTypeObject *type;
? PyObject *obj;
? PyTypeObject *obj_type;
?} superobject;
從代碼段6中可以看到superobject的數(shù)據(jù)成員僅有3個(gè)指針(3個(gè)對(duì)象的引用)。要知道這3個(gè)對(duì)象分別代表什么,則必需考查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", &PyType_Type, &type, &obj))
?? 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把傳入的參數(shù)列表解釋出來,分別放在type和obj變量之中。然后通過supercheck測(cè)試可選參數(shù)obj是否合法,并獲得實(shí)例obj的具體類類型。最后,把type, obj和obj_type記錄下來。也就是說,super對(duì)象只是簡(jiǎn)單作了一些記錄,并沒有作任何轉(zhuǎn)換操作。
查找問題的切入點(diǎn)是為什么在類C中的super調(diào)用會(huì)切換到類D的初始化函數(shù)。于是在super_init中添加條件斷點(diǎn),并跟蹤其后的Python代碼。最終進(jìn)入到super_getattro函數(shù)――對(duì)應(yīng)于super對(duì)象訪問名字__init__時(shí)的搜索操作。
?代碼段8(省略部分無關(guān)代碼,并加入一些注釋):
?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;? // 獲得搜索的起點(diǎn):super對(duì)象的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中的下一個(gè)類
?? res = NULL;
?? for (; i < n; i++) {?? // 在mro以后的各個(gè)命名空間中搜索指定名字
??? 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對(duì)象在搜索命名空間時(shí),其實(shí)是基于類實(shí)例的mro進(jìn)行。那么什么是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中記錄了一個(gè)類的所有基類的類類型序列。查看mro的記錄,發(fā)覺包含7個(gè)元素,7個(gè)類名分別為:
?F E B C D A object
從而說明了為什么在C.__init__中使用super(C, self).__init__()會(huì)調(diào)用類D的初始化函數(shù)了。
我們把代碼段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(xiàn)的初始化不僅完成了所有的父類的調(diào)用,而且保證了每一個(gè)父類的初始化函數(shù)只調(diào)用一次。
三、延續(xù)的討論
我們?cè)僦匦驴瓷厦娴念愺w系圖,如果把每一個(gè)類看作圖的一個(gè)節(jié)點(diǎn),每一個(gè)從子類到父類的直接繼承關(guān)系看作一條有向邊,那么該體系圖將變?yōu)橐粋€(gè)有向圖。不能發(fā)現(xiàn)mro的順序正好是該有向圖的一個(gè)拓?fù)渑判蛐蛄小?
從而,我們得到了另一個(gè)結(jié)果――Python是如何去處理多繼承。支持多繼承的傳統(tǒng)的面向?qū)ο蟪绦蛘Z言(如C++)是通過虛擬繼承的方式去實(shí)現(xiàn)多繼承中父類的構(gòu)造函數(shù)被多次調(diào)用的問題,而Python則通過mro的方式去處理。
但這給我們一個(gè)難題:對(duì)于提供類體系的編寫者來說,他不知道使用者會(huì)怎么使用他的類體系,也就是說,不正確的后續(xù)類,可能會(huì)導(dǎo)致原有類體系的錯(cuò)誤,而且這樣的錯(cuò)誤非常隱蔽的,也難于發(fā)現(xiàn)。
四、小結(jié)
1. super并不是一個(gè)函數(shù),是一個(gè)類名,形如super(B, self)事實(shí)上調(diào)用了super類的初始化函數(shù),
?????? 產(chǎn)生了一個(gè)super對(duì)象;
2. super類的初始化函數(shù)并沒有做什么特殊的操作,只是簡(jiǎn)單記錄了類類型和具體實(shí)例;
3. super(B, self).func的調(diào)用并不是用于調(diào)用當(dāng)前類的父類的func函數(shù);
4. Python的多繼承類是通過mro的方式來保證各個(gè)父類的函數(shù)被逐一調(diào)用,而且保證每個(gè)父類函數(shù)
?????? 只調(diào)用一次(如果每個(gè)類都使用super);
5. 混用super類和非綁定的函數(shù)是一個(gè)危險(xiǎn)行為,這可能導(dǎo)致應(yīng)該調(diào)用的父類函數(shù)沒有調(diào)用或者一
?????? 個(gè)父類函數(shù)被調(diào)用多次。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
