一、問題的發現與提出
在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",? &PySuper_Type);
于是,我們有了對super的第一個誤解:super并非是一個函數,而是一個類(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類只改寫了幾個方法,最主要的包括: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", &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把傳入的參數列表解釋出來,分別放在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類和非綁定的函數是一個危險行為,這可能導致應該調用的父類函數沒有調用或者一
?????? 個父類函數被調用多次。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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