python-super 由Python的super()函數想到的 首先看一下super()函數的定義: 返回一個代理對象, 這個對象負責將方法調用分配給第一個參數的一個父類或者同輩的類去完成. parent or sibling class 如何確定? 第一個參數的__mro__屬性決定了搜索的順 ...
python-super
由Python的super()函數想到的
首先看一下super()
函數的定義:
super([type [,object-or-type]])
Return a **proxy object** that delegates method calls to a **parent or sibling** class of type.
返回一個代理對象, 這個對象負責將方法調用分配給第一個參數的一個父類或者同輩的類去完成.
parent or sibling class 如何確定?
第一個參數的__mro__
屬性決定了搜索的順序, super指的的是 MRO(Method Resolution Order) 中的下一個類, 而不一定是父類!
super()和getattr() 都使用__mro__
屬性來解析搜索順序, __mro__
實際上是一個只讀的元組.
MRO中類的順序是怎麼排的呢?
實際上MRO列表本身是根據一種C3的線性化處理技術確定的, 理論說明可以參考這裡, 這裡只簡單說明一下原則:
在MRO中, 基類永遠出現在派生類的後面, 如果有多個基類, 基類的相對順序不變.
MRO實際上是對繼承樹做層序遍歷的結果, 把一棵帶有結構的樹變成了一個線性的表, 所以沿著這個列表一直往上, 就可以無重覆的遍歷完整棵樹, 也就解決了多繼承中的Diamond問題.
比如說:
class Root:
pass
class A(Root):
pass
class B(Root):
pass
class C(A, B):
pass
print(C.__mro__)
# 輸出結果為:
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Root'>, <class 'object'>)
super()實際返回的是一個代理的super對象!
調用super()這個構造方法時, 只是返回一個super()對象, 並不做其他的操作.
然後對這個super對象進行方法調用時, 發生的事情如下:
- 找到第一個參數的
__mro__
列表中的下一個直接定義了該方法的類, 並實例化出一個對象 - 然後將這個對象的
self
變數綁定到第二個參數上, 返回這個對象
舉個例子:
class Root:
def __init__(self):
print('Root')
class A(Root):
def __init__(self):
super().__init__() # 等同於super(A, self).__init__()
在A
的構造方法中, 先調用super()得到一個super對象
, 然後向這個對象調用init方法, 這是super對象會搜索A
的__mro__
列表, 找到第一個定義了__init__
方法的類, 於是就找到了Root
, 然後調用Root.__init__(self)
, 這裡的self
是super()
的第二個參數, 是編譯器自動填充的, 也就是A
的__init__
的第一個參數, 這樣就完成對__init__
方法調用的分配.
註意: 在許多語言的繼承中, 子類必須調用父類的構造方法, 就是為了保證子類的對象能夠填充上父類的屬性! 而不是初始化一個父類對象...(我之前就一直是這麼理解的..). Python中就好多了, 所謂的調用父類構造方法, 就是明明白白地把self
傳給父類的構造方法, 我的小身子骨就這麼交給你了, 隨便你怎麼折騰吧
參數說明
super() -> same as super(__class__, <first argument>) # <first argument>指的是調用super的函數的第一個參數
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)
Typical use to call a cooperative superclass method:
class C(B):
def meth(self, arg):
super().meth(arg)
This works for class methods too:
class C(B):
@classmethod
def cmeth(cls, arg):
super().cmeth(arg)
- 如果提供了第二個參數, 則找到的父類對象的
self
就綁定到這個參數上, 後面調用這個對象的方法時, 可以自動地隱式傳遞self
.如果第二個參數是一個對象, 則
isinstance(obj, type)
必須為True
. 如果第二個參數為一個類型, 則issubclass(type2, type)
必須為True
- 如果沒有傳遞第二個參數, 那麼返回的對象就是Unbound, 調用這個unbound對象的方法時需要手動傳遞第一個參數, 類似於
Base.__int__(self, a, b)
. - 不帶參數的super()只能用在類定義中(因為依賴於caller的第二個參數), 編譯器會自動根據當前定義的類填充參數.
也就是說, 後面所有調用super返回對象的方法時, 第一個參數self
都是super()
的第二個參數. 因為Python中所謂的方法, 就是一個第一個參數為self的函數, 一般在調用方法的時候a.b()
會隱式的將a
賦給b()
的第一個參數.
super()的兩種常見用法:
- 單繼承中, super用來指代隱式指代父類, 避免直接使用父類的名字
- 多繼承中, 解決Diamond問題 (TODO)
對面向對象的理解
其實我覺得Python裡面這樣的語法更容易理解面向對象的本質, 比Java中隱式地傳this
更容易理解.
所謂函數, 就是一段代碼, 接受輸入, 返回輸出. 所謂方法, 就是一個函數有了一個隱式傳遞的參數. 所以方法就是一段代碼, 是類的所有實例共用的, 唯一不同的是各個實例調用的時候傳給方法的this
或者self
不一樣而已.
構造方法是什麼呢? 其實也是一個實例方法啊, 它只有在對象生成了之後才能調用, 所以Python中__init__
方法的參數是self
啊. 調用構造方法時其實已經為對象分配了記憶體, 構造方法只是起到初始化的作用, 也就是為這段記憶體裡面賦點初值而已.
Java中所謂的靜態變數其實也就是類的變數, 其實也就是為類也分配了記憶體, 裡面存了這些變數, 所以Python中的類對象我覺得是很合理的, 也比Java要直觀. 至於靜態方法, 那就與對象一點關係都沒有了, 本質就是個獨立的函數, 只不過寫在了類裡面而已. 而Python中的classmethod其實也是一種靜態方法, 不過它會依賴於cls
對象, 這個cls
就是類對象, 但是只要想用這個方法, 類對象必然是存在的, 不像實例對象一樣需要手動的實例化, 所以classmethod也可以看做是一種靜態變數. 而staticmethod就是真正的靜態方法了, 是獨立的函數, 不依賴任何對象.
Java中的實例方法是必須依賴於對象存在的, 因為要隱式的傳輸this
, 如果對象不存在這個this
也沒法隱式了. 所以在靜態方法中是沒有this
指針的, 也就沒法調用實例方法. 而Python中的實例方法是可以通過類名來調用的, 只不過因為這時候self
沒辦法隱式傳遞, 所以必須得顯式地傳遞.