首先看一下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__) # 輸出結果為: # (, , , , )
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傳給父類的構造方法, 我的小身子骨就這么交給你了, 隨便你怎么折騰吧:joy:
參數說明
super() -> same as super(__class__,) # 指的是調用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沒辦法隱式傳遞, 所以必須得顯式地傳遞.
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
