Python中super函數的用法
之前看python文檔的時候發現許多單繼承類也用了super()來申明父類,那么這樣做有何意義?
從python官網文檔對於super的介紹來看,其作用為返回一個代理對象作為代表調用父類或親類方法。(Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. )
super()的主要用法有兩種: 在單類繼承中,其意義就是不需要父類的名稱來調用父類的函數,因此當子類改為繼承其他父類的時候,不需要對子類內部的父類調用函數做任何修改就能調用新父類的方法。 比如:
#!/usr/bin/env python2.7 class base1(object): # class base2(object): def __init__(self): # def __init__(self): print "base1 class" # print "base2 class" # 若繼承父類需要換成base2 class A(base1): # class A(base2): def __init__(self): # def __init__(self): base1.__init__(self) # base2.__init__(self) class B(base1): # class B(base2): def __init__(self): # def __init__(self): super(B, self).__init__() # super(B, self).__init__()
這里要注意的是由於super將傳入self作為調用__init__默認的第一個變量,因此在聲明的時候不需要顯式表示self。此外由於super返回的是代理對象,因此父類只能調用一次,也就避免了如下異常的可能性。
base1 = A
base1() # 無限遞歸
而在多類繼承中super()是必不可少的(多類繼承是python的一大特色),super()的**__mro__變量**記錄了方法解析搜索順序,既一個類的所有父類的調用順序(MRO用來保證多類繼承的時候各父類被逐一調用並只被調用一次)。例如:
class minin(base1): def __init__(self): print "mixin" super(mixin, self).__init__() class C(B, mixin): # 1. mixin類插入到了B類和base1類之間 pass ChildC() ChildC.__mro__ # 2. 方法解析順序(MRO): C -> B -> mixin -> base1
在上述調用中,base1類不再是C類實例中B類的父類。如果self是C類實例,super(B, self)將指向mixin類。 對於多類繼承,各子類的調用順序可以參考這篇文章:python super():
我們經常在類的繼承當中使用super(), 來調用父類中的方法。例如下面:
class A: def func(self): print('OldBoy') class B(A): def func(self): super().func() print('LuffyCity') A().func() B().func()
輸出的結果為:
OldBoy
OldBoy
LuffyCity
A實例化的對象調用了func方法,打印輸出了 Oldboy;
B實例化的對象調用了自己的func方法,先調用了父類的方法打印輸出了 OldBoy ,再打印輸出 LuffyCity 。
這樣是Python3的寫法,今天咱們也只討論Python3中的super。
如果不使用super的話,想得到相同的輸出截個,還可以這樣寫B的類:
class B(A):
def func(self):
A.func(self)
print('LuffyCity')
這樣能實現相同的效果,只不過傳了一個self參數。那為什么還要使用super()呢?
那我看看有這樣的一個繼承關系的類(鑽石繼承):
Base
/ \
/ \
A B
\ /
\ /
C
代碼是這樣的:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')
class B(Base):
def __init__(self):
Base.__init__(self)
print('B.__init__')
class C(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print('C.__init__')
C()
輸出的結果是:
Base.__init__ A.__init__ Base.__init__ B.__init__ C.__init__
每個子類都調用父類的__init__方法,想把所有的初始化操作都做一遍,但是出現了一個問題,Base類的__init__方法被調用了兩次,這是多余的操作,也是不合理的。
那我們改寫成使用super()的寫法:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A, B):
def __init__(self):
super().__init__()
print('C.__init__')
C()
輸出的結果是:
Base.__init__ B.__init__ A.__init__ C.__init__
這樣執行的結果就比較滿意,是大多數人想要的結果。那為什么會是這樣的結果呢?
那是因為我們每定義一個類的時候,Python都會創建一個MRO列表,用來管理類的繼承順序。
print(C.mro()) # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>]
Python通過這個列表從左到右,查找繼承的信息。Python3中的類都是新式類,都有這個mro屬性,能看出來是廣度優先的查找原則。經典類就沒有mro屬性,但它的查找原則是深度優先。
那我回到super的問題上來,讓我們先看看super的官方定義。
super([type[, object-or-type]])
返回一個代理對象,該對象將方法調用委托給類的父類或兄弟類。這對於訪問類中已重寫的繼承方法非常有用。搜索順序與getattr()使用的搜索順序相同,只是類型本身被跳過。
類的__mro__屬性列出了getattr()和super()使用的方法解析搜索順序。屬性是動態的,可以在繼承層次結構更新時進行更改。
看到官方的解釋就可以很清楚的明白,super是一個類,實例化之后得到的是一個代理的對象,而不是得到了父類,並且我們使用這個代理對象來調用父類或者兄弟類的方法。
那我們再看看super的使用方法:
super() -> same as super(__class__, <first argument>) super(type) -> unbound super object super(type, obj) -> bound super object; requires isinstance(obj, type) super(type, type2) -> bound super object; requires issubclass(type2, type)
super至少需要一個參數,並且類型需要是類。
不傳參數的會報錯。只傳一個參數的話是一個不綁定的對象,不綁定的話也就沒什么用了。
print(super(C)) print(super())
輸出結果:
RuntimeError: super(): no arguments
<super: <class 'C'>, NULL>
在定義類當中可以不寫參數,Python會自動根據情況將兩個參數傳遞給super。
class C(A, B):
def __init__(self):
print(super())
super().__init__()
print('C.__init__')
C()
輸出結果:
<super: <class 'C'>, <C object>> Base.__init__ B.__init__ A.__init__ C.__init__
所以我們在類中使用super的時候參數是可以省略的。
第三種用法, super(type, obj) 傳遞一個類和對象,得到的是一個綁定的super對象。這還需要obj是type的實例,可以不是直接的實例,是子類的實例也行。
a = A() print(super(A, a)) print(super(Base, a))
輸出結果:
Base.__init__ A.__init__ <super: <class 'A'>, <A object>> <super: <class 'Base'>, <A object>>
第三種用法, super(type, type2)傳遞兩個類,得到的也是一個綁定的super對象。這需要type2是type的子類。
print(super(Base, A)) print(super(Base, B)) print(super(Base, C))
輸出結果:
<super: <class 'Base'>, <A object>> <super: <class 'Base'>, <B object>> <super: <class 'Base'>, <C object>>
接下來我們就該說說查找順序了,兩個參數,是按照那個參數去計算MRO呢?
我們將C類中的super的參數填寫上,並且實例化,看看輸出的結果。
class C(A, B):
def __init__(self):
super(C, self).__init__()
print('C.__init__')
輸出結果:
Base.__init__ B.__init__ A.__init__ C.__init__
看結果和之前super沒填參數的結果是一樣的。
那我們將super的第一個參數改為A:
class C(A, B):
def __init__(self):
super(A, self).__init__()
print('C.__init__')
輸出結果:
Base.__init__
B.__init__
C.__init__
咦!?那A.__init__怎么跑丟了呢?多出來了B.__init__呢?
這是應為Python是按照第二個參數來計算MRO,這次的參數是self,也就是C的MRO。在這個順序中跳過一個參數(A)找后面一個類(B),執行他的方法。
知道這個后,輸出的結果就可以理解了。 super(A, self).__init__() 沒有執行Base的方法,而是執行了B的方法。
那我們接下來說說 super(type, obj) 和 super(type, type2)的區別。
代碼如下:
class Base:
def func(self):
return 'from Base'
class A(Base):
def func(self):
return 'from A'
class B(Base):
def func(self):
return 'from B'
class C(A, B):
def func(self):
return 'from C'
c_obj = C()
print(super(C, C))
print(super(C, c_obj))
輸出結果:
<super: <class 'C'>, <C object>> <super: <class 'C'>, <C object>>
兩次的打印結果一模一樣,verygood。那他們的方法是否是一樣的呢?測試一下。
print(super(C, C).func is super(C, c_obj).func) print(super(C, C).func == super(C, c_obj).func)
輸出結果:
False
False
他倆的方法既不是指向同一個,值還不相等。是不是搞錯了呢?再試試下面的看看。
c1 = super(C, C) c2 = super(C, C) print(c1 is c2) print(c1 == c2) print(c1.func is c2.func) print(c1.func == c2.func)
輸出結果:
False
False
True
True
c1和c2不是一個對象,但是他們的方法卻是相同的。
那 super(C, C).func 和 super(C, c_obj).func 的確是不同的。那打印出來看看有什么區別:
print(super(C, C).func) print(super(C, c_obj).func)
輸出結果:
<function A.func at 0x0000000009F4D6A8>
<bound method A.func of <__main__.C object at 0x00000000022A94E0>>
super的第二個參數傳遞的是類,得到的是函數。
super的第二個參數傳遞的是對象,得到的是綁定方法。
函數和綁定方法的區別就不再贅述了,在這里想得到一樣的結果,只需要給函數傳遞一個參數,而綁定方法則不需要傳遞額外的參數了。
print(super(C, C).func(c_obj)) print(super(C, c_obj).func())
輸出結果:
from A from A
那我現在總結一下:
- super()使用的時候需要傳遞兩個參數,在類中可以省略不寫,我們使用super()來找父類或者兄弟類的方法;
- super()是根據第二個參數來計算MRO,根據順序查找第一個參數類后的方法。
- super()第二個參數是類,得到的方法是函數,使用時要傳self參數。第二個參數是對象,得到的是綁定方法,不需要再傳self參數。
給使用super()的一些建議:
- super()調用的方法要存在;
- 傳遞參數的時候,盡量使用*args 與**kwargs;
- 父類中的一些特性,比如【】、重寫了__getattr__,super對象是不能使用的。
- super()第二個參數傳的是類的時候,建議調用父類的類方法和靜態方法。
