亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Python中的super用法詳解

 更新時(shí)間:2015年05月28日 16:00:15   投稿:junjie  
這篇文章主要介紹了Python中的super用法詳解,本文講解了關(guān)于super問(wèn)題的發(fā)現(xiàn)與提出、走進(jìn)Python的源碼世界分析super的實(shí)現(xiàn)、延續(xù)的討論super等內(nèi)容,需要的朋友可以參考下

一、問(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ì)象訪問(wèn)名字__init__時(shí)的搜索操作。

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

復(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ù)渑判蛐蛄小?/p>

  從而,我們得到了另一個(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)用多次。

相關(guān)文章

最新評(píng)論