<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當(dāng)前位置: 首頁 - 科技 - 知識百科 - 正文

        Python中的super用法詳解

        來源:懂視網(wǎng) 責(zé)編:小采 時間:2020-11-27 14:33:51
        文檔

        Python中的super用法詳解

        Python中的super用法詳解:一、問題的發(fā)現(xiàn)與提出 在Python類的方法(method)中,要調(diào)用父類的某個方法,在Python 2.2以前,通常的寫法如代碼段1: 代碼段1: 代碼如下: class A: def __init__(self): print enter A print leave A class B(A)
        推薦度:
        導(dǎo)讀Python中的super用法詳解:一、問題的發(fā)現(xiàn)與提出 在Python類的方法(method)中,要調(diào)用父類的某個方法,在Python 2.2以前,通常的寫法如代碼段1: 代碼段1: 代碼如下: class A: def __init__(self): print enter A print leave A class B(A)

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

        在Python類的方法(method)中,要調(diào)用父類的某個方法,在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ù)列表中,引入待綁定的對象(self),從而達到調(diào)用父類的目的。

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

        代碼段2:
        代碼如下:


        class B(C): # A --> C
        def __init__(self):
        print "enter B"
        C.__init__(self) # A --> C
        print "leave B"


        如果代碼簡單,這樣的改動或許還可以接受。但如果代碼量龐大,這樣的修改可能是災(zāi)難性的。

          因此,自Python 2.2開始,Python添加了一個關(guān)鍵字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"

         嘗試執(zhí)行上面同樣的代碼,結(jié)果一致,但修改的代碼只有一處,把代碼的維護量降到最低,是一個不錯的用法。因此在我們的開發(fā)過程中,super關(guān)鍵字被大量使用,而且一直表現(xiàn)良好。

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

          有一天某同事設(shè)計了一個相對復(fù)雜的類體系結(jié)構(gòu)(我們先不要管這個類體系設(shè)計得是否合理,僅把這個例子作為一個題目來研究就好),代碼如代碼段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次——其實這是多繼承的類體系必須面對的問題。我們把代碼段4的類體系畫出來,如下圖:
        代碼如下:


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


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

        二、走進Python的源碼世界

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

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

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


        SETBUILTIN("super", &PySuper_Type);


          于是,我們有了對super的第一個誤解:super并非是一個函數(shù),而是一個類(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的數(shù)據(jù)成員僅有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把傳入的參數(shù)列表解釋出來,分別放在type和obj變量之中。然后通過supercheck測試可選參數(shù)obj是否合法,并獲得實例obj的具體類類型。最后,把type, obj和obj_type記錄下來。也就是說,super對象只是簡單作了一些記錄,并沒有作任何轉(zhuǎn)換操作。

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

        代碼段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; // 獲得搜索的起點: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的記錄,發(fā)覺包含7個元素,7個類名分別為:
        代碼如下:


        F E B C D A object


          從而說明了為什么在C.__init__中使用super(C, self).__init__()會調(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)用,而且保證了每一個父類的初始化函數(shù)只調(diào)用一次。

        三、延續(xù)的討論

          我們再重新看上面的類體系圖,如果把每一個類看作圖的一個節(jié)點,每一個從子類到父類的直接繼承關(guān)系看作一條有向邊,那么該體系圖將變?yōu)橐粋€有向圖。不能發(fā)現(xiàn)mro的順序正好是該有向圖的一個拓撲排序序列。

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

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

        四、小結(jié)

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

        聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        Python中的super用法詳解

        Python中的super用法詳解:一、問題的發(fā)現(xiàn)與提出 在Python類的方法(method)中,要調(diào)用父類的某個方法,在Python 2.2以前,通常的寫法如代碼段1: 代碼段1: 代碼如下: class A: def __init__(self): print enter A print leave A class B(A)
        推薦度:
        標(biāo)簽: 使用 用法 使用的
        • 熱門焦點

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 亚洲综合国产一区二区三区| 成人午夜免费福利| 在线亚洲97se亚洲综合在线| 免费又黄又爽又猛大片午夜 | 午夜亚洲福利在线老司机| 亚洲免费福利在线视频| 成全视频免费高清| 国产AV无码专区亚洲AV蜜芽| 国产成人精品免费视频软件| 妇女自拍偷自拍亚洲精品| 亚洲国产日韩在线观频| CAOPORN国产精品免费视频| 亚洲成在人线av| 91久久青青草原线免费| 亚洲av永久无码精品三区在线4| 中字幕视频在线永久在线观看免费| 亚洲国产日韩在线一区| 日本高清色本免费现在观看| 日韩少妇内射免费播放| 国产亚洲综合一区柠檬导航| 99久久人妻精品免费一区| 亚洲免费电影网站| 日本免费无遮挡吸乳视频电影| 免费观看四虎精品成人| 日韩亚洲人成在线综合日本| 精品无码AV无码免费专区| 亚洲欧美乱色情图片| 亚洲精品字幕在线观看| 青娱乐免费在线视频| 水蜜桃视频在线观看免费| 亚洲精品高清国产一线久久| 69av免费观看| 综合一区自拍亚洲综合图区 | 亚洲美女视频网址| 麻豆国产入口在线观看免费| 国产精品高清免费网站| 亚洲激情校园春色| 免费国产高清视频| 88av免费观看入口在线| 亚洲A∨精品一区二区三区下载| 国产av无码专区亚洲av果冻传媒|