>>b=B()enterBenterAleaveAlea" />

黄色网页视频 I 影音先锋日日狠狠久久 I 秋霞午夜毛片 I 秋霞一二三区 I 国产成人片无码视频 I 国产 精品 自在自线 I av免费观看网站 I 日本精品久久久久中文字幕5 I 91看视频 I 看全色黄大色黄女片18 I 精品不卡一区 I 亚洲最新精品 I 欧美 激情 在线 I 人妻少妇精品久久 I 国产99视频精品免费专区 I 欧美影院 I 欧美精品在欧美一区二区少妇 I av大片网站 I 国产精品黄色片 I 888久久 I 狠狠干最新 I 看看黄色一级片 I 黄色精品久久 I 三级av在线 I 69色综合 I 国产日韩欧美91 I 亚洲精品偷拍 I 激情小说亚洲图片 I 久久国产视频精品 I 国产综合精品一区二区三区 I 色婷婷国产 I 最新成人av在线 I 国产私拍精品 I 日韩成人影音 I 日日夜夜天天综合

Python中的super用法詳解

系統(tǒng) 2043 0

一、問(wèn)題的發(fā)現(xiàn)與提出

在Python類(lèi)的方法(method)中,要調(diào)用父類(lèi)的某個(gè)方法,在Python 2.2以前,通常的寫(xiě)法如代碼段1:

代碼段1:

復(fù)制代碼 代碼如下:

?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


即,使用非綁定的類(lèi)方法(用類(lèi)名來(lái)引用的方法),并在參數(shù)列表中,引入待綁定的對(duì)象(self),從而達(dá)到調(diào)用父類(lèi)的目的。

  這樣做的缺點(diǎn)是,當(dāng)一個(gè)子類(lèi)的父類(lèi)發(fā)生變化時(shí)(如類(lèi)B的父類(lèi)由A變?yōu)镃時(shí)),必須遍歷整個(gè)類(lèi)定義,把所有的通過(guò)非綁定的方法的類(lèi)名全部替換過(guò)來(lái),例如代碼段2,

?代碼段2:

復(fù)制代碼 代碼如下:

?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開(kāi)始,Python添加了一個(gè)關(guān)鍵字super,來(lái)解決這個(gè)問(wèn)題。下面是Python 2.3的官方文檔說(shuō)明:

復(fù)制代碼 代碼如下:

?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.


  從說(shuō)明來(lái)看,可以把類(lèi)B改寫(xiě)如代碼段3:

?代碼段3:

復(fù)制代碼 代碼如下:

?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ò)的用法。因此在我們的開(kāi)發(fā)過(guò)程中,super關(guān)鍵字被大量使用,而且一直表現(xiàn)良好。

  在我們的印象中,對(duì)于super(B, self).__init__()是這樣理解的:super(B, self)首先找到B的父類(lèi)(就是類(lèi)A),然后把類(lèi)B的對(duì)象self轉(zhuǎn)換為類(lèi)A的對(duì)象(通過(guò)某種方式,一直沒(méi)有考究是什么方式,慚愧),然后“被轉(zhuǎn)換”的類(lèi)A對(duì)象調(diào)用自己的__init__函數(shù)。考慮到super中只有指明子類(lèi)的機(jī)制,因此,在多繼承的類(lèi)定義中,通常我們保留使用類(lèi)似代碼段1的方法。

  有一天某同事設(shè)計(jì)了一個(gè)相對(duì)復(fù)雜的類(lèi)體系結(jié)構(gòu)(我們先不要管這個(gè)類(lèi)體系設(shè)計(jì)得是否合理,僅把這個(gè)例子作為一個(gè)題目來(lái)研究就好),代碼如代碼段4:

代碼段4:

復(fù)制代碼 代碼如下:

?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


  明顯地,類(lèi)A和類(lèi)D的初始化函數(shù)被重復(fù)調(diào)用了2次,這并不是我們所期望的結(jié)果!我們所期望的結(jié)果是最多只有類(lèi)A的初始化函數(shù)被調(diào)用2次――其實(shí)這是多繼承的類(lèi)體系必須面對(duì)的問(wèn)題。我們把代碼段4的類(lèi)體系畫(huà)出來(lái),如下圖:
復(fù)制代碼 代碼如下:

??? object
?? |?????? /
?? |??????? A
?? |????? / |
?? B? C? D
??? /?? /?? |
????? E??? |
??????? /?? |
????????? F

  按我們對(duì)super的理解,從圖中可以看出,在調(diào)用類(lèi)C的初始化函數(shù)時(shí),應(yīng)該是調(diào)用類(lèi)A的初始化函數(shù),但事實(shí)上卻調(diào)用了類(lèi)D的初始化函數(shù)。好一個(gè)詭異的問(wèn)題!

二、走進(jìn)Python的源碼世界

  我們嘗試改寫(xiě)代碼段4中的函數(shù)調(diào)用,但都沒(méi)有得到我們想要的結(jié)果,這不得不使我們開(kāi)始懷疑:我們對(duì)super的理解是否出了問(wèn)題。

  我們重新閱讀了Python的官方文檔,正如您所見(jiàn),官方文檔并沒(méi)有詳細(xì)的原理說(shuō)明。到網(wǎng)絡(luò)上去搜索,確實(shí)有人發(fā)現(xiàn)了同樣的問(wèn)題,并在一些論壇中討論,但似乎并沒(méi)有實(shí)質(zhì)性的解答。既然,沒(méi)有前人的足跡,我們只好走進(jìn)Python的源碼世界,去追溯問(wèn)題的根源。

  我們考查的是Python 2.3的源碼(估計(jì)Python 2.4的源碼可能也差不多)。首先,搜索關(guān)鍵字"super"。唯一找到的是bltinmodule.c中的一句:

復(fù)制代碼 代碼如下:

?SETBUILTIN("super",? &PySuper_Type);

  于是,我們有了對(duì)super的第一個(gè)誤解:super并非是一個(gè)函數(shù),而是一個(gè)類(lèi)(PySuper_Type)。

  在typeobject.c中找到了PySuper_Type的定義:

?代碼段5:

復(fù)制代碼 代碼如下:

?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類(lèi)只改寫(xiě)了幾個(gè)方法,最主要的包括:tp_dealloc,tp_getattro,tp_traverse,tp_init。

  再看superobject的定義:

?代碼段6:

復(fù)制代碼 代碼如下:

?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:

復(fù)制代碼 代碼如下:

?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首先通過(guò)PyArg_ParseTuple把傳入的參數(shù)列表解釋出來(lái),分別放在type和obj變量之中。然后通過(guò)supercheck測(cè)試可選參數(shù)obj是否合法,并獲得實(shí)例obj的具體類(lèi)類(lèi)型。最后,把type, obj和obj_type記錄下來(lái)。也就是說(shuō),super對(duì)象只是簡(jiǎn)單作了一些記錄,并沒(méi)有作任何轉(zhuǎn)換操作。

  查找問(wèn)題的切入點(diǎn)是為什么在類(lèi)C中的super調(diào)用會(huì)切換到類(lèi)D的初始化函數(shù)。于是在super_init中添加條件斷點(diǎn),并跟蹤其后的Python代碼。最終進(jìn)入到super_getattro函數(shù)――對(duì)應(yīng)于super對(duì)象訪(fǎng)問(wèn)名字__init__時(shí)的搜索操作。

?代碼段8(省略部分無(wú)關(guān)代碼,并加入一些注釋?zhuān)?

復(fù)制代碼 代碼如下:

?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;? // 獲得類(lèi)的mro
?? ……
?? for (i = 0; i < n; i++) {? // 搜索mro中,定位mro中的type
??? if ((PyObject *)(su->type) == PyTuple_GET_ITEM(mro, i))
???? break;
?? }
?? i++;?????? // 切換到mro中的下一個(gè)類(lèi)
?? 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í)是基于類(lèi)實(shí)例的mro進(jìn)行。那么什么是mro呢?查找官方文檔,有:
復(fù)制代碼 代碼如下:

?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().


  也就是說(shuō),mro中記錄了一個(gè)類(lèi)的所有基類(lèi)的類(lèi)類(lèi)型序列。查看mro的記錄,發(fā)覺(jué)包含7個(gè)元素,7個(gè)類(lèi)名分別為:
復(fù)制代碼 代碼如下:

?F E B C D A object

  從而說(shuō)明了為什么在C.__init__中使用super(C, self).__init__()會(huì)調(diào)用類(lèi)D的初始化函數(shù)了。

  我們把代碼段4改寫(xiě)為:

?代碼段9:

復(fù)制代碼 代碼如下:

?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)的初始化不僅完成了所有的父類(lèi)的調(diào)用,而且保證了每一個(gè)父類(lèi)的初始化函數(shù)只調(diào)用一次。

三、延續(xù)的討論

  我們?cè)僦匦驴瓷厦娴念?lèi)體系圖,如果把每一個(gè)類(lèi)看作圖的一個(gè)節(jié)點(diǎn),每一個(gè)從子類(lèi)到父類(lèi)的直接繼承關(guān)系看作一條有向邊,那么該體系圖將變?yōu)橐粋€(gè)有向圖。不能發(fā)現(xiàn)mro的順序正好是該有向圖的一個(gè)拓?fù)渑判蛐蛄小?

  從而,我們得到了另一個(gè)結(jié)果――Python是如何去處理多繼承。支持多繼承的傳統(tǒng)的面向?qū)ο蟪绦蛘Z(yǔ)言(如C++)是通過(guò)虛擬繼承的方式去實(shí)現(xiàn)多繼承中父類(lèi)的構(gòu)造函數(shù)被多次調(diào)用的問(wèn)題,而Python則通過(guò)mro的方式去處理。

  但這給我們一個(gè)難題:對(duì)于提供類(lèi)體系的編寫(xiě)者來(lái)說(shuō),他不知道使用者會(huì)怎么使用他的類(lèi)體系,也就是說(shuō),不正確的后續(xù)類(lèi),可能會(huì)導(dǎo)致原有類(lèi)體系的錯(cuò)誤,而且這樣的錯(cuò)誤非常隱蔽的,也難于發(fā)現(xiàn)。

四、小結(jié)

  1. super并不是一個(gè)函數(shù),是一個(gè)類(lèi)名,形如super(B, self)事實(shí)上調(diào)用了super類(lèi)的初始化函數(shù),
?????? 產(chǎn)生了一個(gè)super對(duì)象;
  2. super類(lèi)的初始化函數(shù)并沒(méi)有做什么特殊的操作,只是簡(jiǎn)單記錄了類(lèi)類(lèi)型和具體實(shí)例;
  3. super(B, self).func的調(diào)用并不是用于調(diào)用當(dāng)前類(lèi)的父類(lèi)的func函數(shù);
  4. Python的多繼承類(lèi)是通過(guò)mro的方式來(lái)保證各個(gè)父類(lèi)的函數(shù)被逐一調(diào)用,而且保證每個(gè)父類(lèi)函數(shù)
?????? 只調(diào)用一次(如果每個(gè)類(lèi)都使用super);
  5. 混用super類(lèi)和非綁定的函數(shù)是一個(gè)危險(xiǎn)行為,這可能導(dǎo)致應(yīng)該調(diào)用的父類(lèi)函數(shù)沒(méi)有調(diào)用或者一
?????? 個(gè)父類(lèi)函數(shù)被調(diào)用多次。


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

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

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論