python super()

【自注】首先一點與super()不太相關的,子類的實例能夠被視爲父類的一個實例ide

super(type[, obj-or-type])函數

給出 type,super()「返回此 type 的父類」。若是你但願父類被綁定,你能夠傳入 obj 參數(obj必須是 type 類型的).不然父類不會被綁定。obj 參數也能夠是一個類型,但它應當是 type 的一個子類。一般,當給出 obj 時:
  若是 obj 是一個實例,isinstance(obj,type)就必須返回 True。此時super()返回父類的一個實例
  若是 obj 是一個類或類型,issubclass(obj,type)就必須返回 True。此時返回父類(暫時這麼理解,可能不太準確)
事實上,super()是一個工廠函數,它創造了一個 super object,爲一個給定的類使用__mro__去查找相應的父類。很明顯,它從當前所找到的類開始搜索 MRO。oop

 

 

 

1、問題的發現與提出spa

  在Python類的方法(method)中,要調用父類的某個方法,在Python 2.2之前,一般的寫法如代碼段1:設計

 代碼段1:3d

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

複製代碼
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),從而達到調用父類的目的。code

  這樣作的缺點是,當一個子類的父類發生變化時(如類B的父類由A變爲C時),必須遍歷整個類定義,把全部的經過非綁定的方法的類名所有替換過來,例如代碼段2,對象

 代碼段2:blog

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()其實是返回了一個父類的實例,只有實例才能夠不傳self調用方法】。考慮到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"

複製代碼
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() result:

 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

複製代碼
 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的初始化函數。好一個詭異的問題!

  也就是說,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"

複製代碼
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() result:

複製代碼
 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的初始化不只完成了全部的父類的調用,並且保證了每個父類的初始化函數只調用一次。

  再看類結構:

複製代碼
    object
     /   \
    /      A
   |     /   \
  B-1  C-2   D-2
    \   /    /
     E-1    /
        \  /
          F
複製代碼

E-1,D-2是F的父類,其中表示E類在前,即F(E,D)。

因此初始化順序能夠從類結構圖來看出 : F->E->B -->C --> D --> A

因爲C,D有同一個父類,所以會先初始化D再是A。

3、延續的討論

  咱們再從新看上面的類體系圖,若是把每個類看做圖的一個節點,每個從子類到父類的直接繼承關係看做一條有向邊,那麼該體系圖將變爲一個有向圖。不能發現mro的順序正好是該有向圖的一個拓撲排序序列。

  從而,咱們獲得了另外一個結果——Python是如何去處理多繼承。支持多繼承的傳統的面向對象程序語言(如C++)是經過虛擬繼承的方式去實現多繼承中父類的構造函數被屢次調用的問題,而Python則經過mro的方式去處理。

  但這給咱們一個難題:對於提供類體系的編寫者來講,他不知道使用者會怎麼使用他的類體系,也就是說,不正確的後續類,可能會致使原有類體系的錯誤,並且這樣的錯誤很是隱蔽的,也難於發現。

4、小結

  1. super並非一個函數,是一個類名,形如super(B, self)事實上調用了super類的初始化函數,
       產生了一個super對象;
  2. super類的初始化函數並無作什麼特殊的操做,只是簡單記錄了類類型和具體實例;
  3. super(B, self).func的調用並非用於調用當前類的父類的func函數;
  4. Python的多繼承類是經過mro的方式來保證各個父類的函數被逐一調用,並且保證每一個父類函數
       只調用一次(若是每一個類都使用super);
  5. 混用super類和非綁定的函數是一個危險行爲,這可能致使應該調用的父類函數沒有調用或者一
       個父類函數被調用屢次。

相關文章
相關標籤/搜索