python super()

轉自:http://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html 

一、問題的發現與提出

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

  也就是說,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類和非綁定的函數是一個危險行爲,這可能導致應該調用的父類函數沒有調用或者一
       個父類函數被調用多次。

附錄(以下內容是我收集的,不是轉自原文):1. 純python實現的一個Super類[1]:

class Super(object):
    def __init__(self, type, obj=None):
        self.__type__ = type
        self.__obj__ = obj
    def __get__(self, obj, type=None):
        if self.__obj__ is None and obj is not None:
            return Super(self.__type__, obj)
        else:
            return self
    def __getattr__(self, attr):
        if isinstance(self.__obj__, self.__type__):
            starttype = self.__obj__.__class__
        else:
            starttype = self.__obj__
        mro = iter(starttype.__mro__)
        for cls in mro:
            if cls is self.__type__:
                break
        # Note: mro is an iterator, so the second loop
        # picks up where the first one left off!
        for cls in mro:
            if attr in cls.__dict__:
                x = cls.__dict__[attr]
                if hasattr(x, "__get__"):
                    x = x.__get__(self.__obj__)
                return x
        raise AttributeError, attr

class A(object):
    def m(self):
        return "A"

class B(A):
    def m(self):
        return "B" + Super(B, self).m()

class C(A):
    def m(self):
        return "C" + Super(C, self).m()

class D(C, B):
    def m(self):
        return "D" + Super(D, self).m()

print D().m() # "DCBA"

2. super的一些缺陷[2]:

1)參數不同可能導致的問題,上示例,

class A(object): 
  def __init__(self): 
    print "A" 
    super(A, self).__init__() 
class B(object): 
  def __init__(self): 
    print "B" 
    super(B, self).__init__() 
class C(A): 
  def __init__(self, arg): 
    print "C","arg=",arg 
    super(C, self).__init__() 
class D(B): 
  def __init__(self, arg): 
    print "D", "arg=",arg 
    super(D, self).__init__() 
class E(C,D): 
  def __init__(self, arg): 
    print "E", "arg=",arg 
    super(E, self).__init__(arg) 
print "MRO:", [x.__name__ for x in E.__mro__] 
E(10) 
輸出結果爲:
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
Traceback (most recent call last):
  File "example1-2.py", line 27, in ?
    E(10)
  File "example1-2.py", line 24, in __init__
    super(E, self).__init__(arg)
  File "example1-2.py", line 14, in __init__
    super(C, self).__init__()
  File "example1-2.py", line 4, in __init__
    super(A, self).__init__()
TypeError: __init__() takes exactly 2 arguments (1 given)


如下圖所示, 導致錯誤的主要原因爲A的__init__調用的是D的__init__, 而不是objcet的__init__, 記住super對象是依據__mro__的順序依次調用,並一定就是當前類的父類。

遇到參數不同,具體的解決辦法是使用*args 和**kwargs 傳參,上例中可以正確運行的代碼爲:

class A(object): 
  def __init__(self,*args, **kwargs): 
    print "A" 
    try:
      super(A, self).__init__(*args, **kwargs)
    except:
      super(A, self).__init__( )
class B(object): 
  def __init__(self,*args, **kwargs): 
    print "B" 
    try:
      super(B, self).__init__(*args, **kwargs)
    except:
      super(B, self).__init__( )
class C(A): 
  def __init__(self, arg,*args, **kwargs): 
    print "C","arg=",arg 
    super(C, self).__init__(arg,*args, **kwargs) 
class D(B): 
  def __init__(self, arg,*args, **kwargs): 
    print "D", "arg=",arg 
    super(D, self).__init__(arg,*args, **kwargs) 
class E(C,D): 
  def __init__(self, arg,*args, **kwargs): 
    print "E", "arg=",arg 
    super(E, self).__init__(arg,*args, **kwargs) 
print "MRO:", [x.__name__ for x in E.__mro__] 
E(10) 

需要說明的是, object對象初始化時除了對象本身(super的第二個參數)不接受其他任何參數的,所以在A和B類中使用了try...except..語句。

 2. 正如在轉載文章中所指出的一樣,不用混用super和未綁定的函數,要用super都同得用super,上例子

class A(object): 
  def __init__(self):
    print "A"
    super(A, self).__init__() 
    pass 
class B(object): 
  def __init__(self): 
    print "B"
    super(B, self).__init__()
    pass
# some other module defines this class, not knowing about super()
class C(A,B): 
  def __init__(self): 
    print "C" 
    A.__init__(self) 
    B.__init__(self) 
    pass 
print "MRO:", [x.__name__ for x in C.__mro__]
print "Method Calls:"
C() 

 運行的結果爲:

MRO: ['C', 'A', 'B', 'object']
Method Calls:
C
A
B
B

很清楚,B類的__init__被調用了兩次,這就是由於混用super和未綁定函數A.__init__和B.__init__所造成的。
  上例中是子類沒有調用super,而是使用了傳統的未綁定函數,其實如果是父類中不用,而子類中不用也會導致非預期的問題,如下例,

class A(object):
  def __init__(self): 
    print "A"
    super(A, self).__init__()
class B(object): 
  def __init__(self): 
    print "B"
    super(B, self).__init__()
class N(object): 
  def __init__(self):
    print "N"
    object.__init__(self)
class M(object): 
  def __init__(self):
    print "M"
    object.__init__(self)
class F1(A,B,N): 
  def __init__(self):
    print "F1"
    super(F1, self).__init__()
class F2(A,N,B):
  def __init__(self):
    print "F2"
    super(F2, self).__init__() 
class F3(N,A,B): 
  def __init__(self): 
    print "F3"
    super(F3, self).__init__() 
class F4(A,B,N,M):
  def __init__(self): 
    print "F3"
    super(F4, self).__init__()
def test(cl): 
  print "MRO:", [x.__name__ for x in cl.__mro__]
  print "Calls:"
  cl()
test(F1)
test(F2)
test(F3)
test(F4) 

輸出結果爲:
MRO: ['F1', 'A', 'B', 'N', 'object']
Calls:
F1
A
B
N
MRO: ['F2', 'A', 'N', 'B', 'object']
Calls:
F2
A
N
MRO: ['F3', 'N', 'A', 'B', 'object']
Calls:
F3
N
MRO: ['F4', 'A', 'B', 'N', 'M', 'object']
Calls:
F3
A
B
N


 

 參考文獻:

[1]http://www.python.org/download/releases/2.2.3/descrintro/#cooperation

[2]https://fuhm.net/super-harmful/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章