python中的繼承機制super()函數(shù)詳解
前言
super 是用來解決多重繼承問題的,直接用類名調(diào)用父類方法在使用單繼承的時候沒問題
但是如果使用多繼承,會涉及到查找順序(MRO)、重復(fù)調(diào)用(鉆石繼承)等種種問題。
一、super用法
我們先簡單的理解為super().xx相當(dāng)于調(diào)用了父類中的xx方法(實際上在單繼承中是這樣,多繼承中有點區(qū)別)。
時候會看到像下面這樣直接調(diào)用父類的一個方法:
class Base: def __init__(self): print('Base.__init__') class A(Base): def __init__(self): Base.__init__(self) print('A.__init__')
盡管對于大部分代碼而言這么做沒什么問題,但是在更復(fù)雜的涉及到多繼承的代碼中就有可能導(dǎo)致很奇怪的問題發(fā)生。
比如,考慮如下的情況:
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 = C()
如果你運行這段代碼就會發(fā)現(xiàn) Base.__init__()
被調(diào)用兩次,如下所示:
Base.__init__ A.__init__ Base.__init__ B.__init__ C.__init__
但是當(dāng)我們在代碼中換成使用 super()
,結(jié)果就很完美了:
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__() # Only one call to super() here print('C.__init__')
運行這個新版本后,你會發(fā)現(xiàn)每個 __init__()
方法只會被調(diào)用一次了:
Base.__init__ B.__init__ A.__init__ C.__init__
二、super的本質(zhì)
先說說python中如何實現(xiàn)繼承---------對于你定義的每一個類,Python會計算出一個所謂的方法解析順序(MRO)列表。 這個MRO列表就是一個簡單的所有基類的線性順序表。為了實現(xiàn)繼承,Python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構(gòu)造是通過一個C3線性化算法來實現(xiàn)的。 我們不去深究這個算法的數(shù)學(xué)原理,它實際上就是合并所有父類的MRO列表并遵循如下三條準(zhǔn)則:
- 子類會先于父類被檢查
- 多個父類會根據(jù)它們在列表中的順序被檢查
- 如果對下一個類存在兩個合法的選擇,選擇第一個父類
雖然名義上來說super是用來調(diào)用父類中的方法,但是super實際上是在MRO表中找到下一個匹配的類。super原型如下:
def super(cls, inst): mro = inst.__class__.mro() return mro[mro.index(cls) + 1]
兩個參數(shù) cls 和 inst 分別做了兩件事:
1. inst 負(fù)責(zé)生成 MRO 的 list
2. 通過 cls 定位當(dāng)前 MRO 中的 index, 并返回 mro[index + 1]
我們來看一個例子,猜猜下面的輸出會是什么呢:
class A(): def __init__(self): print("Enter A") class B(A): def __init__(self): print("Enter B") super(B,self).__init__() print("Leave B") class C(A): def __init__(self): print("Enter C") super(C,self).__init__() print("Leave C") class D(B,C): def __init__(self): print("Enter D") super(D,self).__init__() print("Leave D") d = D()
直接看結(jié)果:
很多人將super簡單的理解為調(diào)用父類中的方法,可能認(rèn)為應(yīng)該是D調(diào)用B和C,由于B在左邊,按順序先調(diào)用B,B油調(diào)用A,完成之后輪到D調(diào)用C,C調(diào)用A.輸出變成下面這樣:
Enter D
Enter B
Enter A
Leave B
Enter C
Enter A
Leave C
Leave D
但是根據(jù)我們上面說的super本質(zhì)知道super 和父類其實沒有實質(zhì)關(guān)聯(lián),我們就不難理解為什么 enter B 下一句是 enter C 而不是 enter A了(如果認(rèn)為 super 代表“調(diào)用父類的方法”,會想當(dāng)然的認(rèn)為下一句應(yīng)該是enter A)。流程如下,在 B 的 __init__ 函數(shù)中:
super(B,self).__init__() 首先獲取self.__class__.__mro__,但是這里的self是D的實例,而不是B的。
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
然后,通過 B 來定位 MRO 中的 index,并找到下一個。
顯然 B 的下一個是 C。于是,我們調(diào)用 C 的 __init__,打出 enter C。
當(dāng)你使用super()
函數(shù)時,Python會在MRO列表上繼續(xù)搜索下一個類。
只要每個重定義的方法統(tǒng)一使用super()
并只調(diào)用它一次, 那么控制流最終會遍歷完整個MRO列表,每個方法也只會被調(diào)用一次。
到此這篇關(guān)于python中的繼承機制super()函數(shù)詳解的文章就介紹到這了,更多相關(guān)python繼承super()函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決python 3 urllib 沒有 urlencode 屬性的問題
今天小編就為大家分享一篇解決python 3 urllib 沒有 urlencode 屬性的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08python 如何把docker-compose.yaml導(dǎo)入到數(shù)據(jù)庫相關(guān)條目里
這篇文章主要介紹了python 如何把docker-compose.yaml導(dǎo)入到數(shù)據(jù)庫相關(guān)條目里?下面小編就為大家介紹一下實現(xiàn)方式,一起跟隨小編過來看看吧2021-01-01基于Tensorflow搭建一個神經(jīng)網(wǎng)絡(luò)的實現(xiàn)
神經(jīng)網(wǎng)絡(luò)可能會讓人感到恐懼,特別是對于新手機器學(xué)習(xí)的人來說。這篇文章主要介紹了基于Tensorflow搭建一個神經(jīng)網(wǎng)絡(luò)的實現(xiàn),從入門開始,感興趣的可以了解一下2021-05-05