深入super,看Python如何解决钻石继承难题
1.?? Python的繼承以及調(diào)用父類成員
python子類調(diào)用父類成員有2種方法,分別是普通方法和super方法
假設(shè)Base是基類
class Base(object):def __init__(self):print “Base init”則普通方法如下
class Leaf(Base):def __init__(self):Base.__init__(self)print “Leaf init”super方法如下
class Leaf(Base):def __init__(self):super(Leaf, self).__init__()print “Leaf init”在上面的簡單場景下,兩種方法的效果一致:
>>> leaf = Leaf()
Base init
Leaf init
2.?? 鉆石繼承遇到的難題
當(dāng)我們來到鉆石繼承場景時,我們就遇到了一個難題:
如果我們還是使用普通方法調(diào)用父類成員,代碼如下:
class Base(object):def __init__(self):print “Base init”class Medium1(Base):def __init__(self):Base.__init__(self)print “Medium1 init”class Medium2(Base):def __init__(self):Base.__init__(self)print “Medium2 init”class Leaf(Medium1, Medium2):def __init__(self):Medium1.__init__(self)Medium2.__init__(self)print “Leaf init”當(dāng)我們生成Leaf對象時,結(jié)果如下:
>>> leaf = Leaf()
Base init
Medium1 init
Base init
Medium2 init
Leaf init
?
可以看到Base被初始化了兩次!這是由于Medium1和Medium2各自調(diào)用了Base的初始化函數(shù)導(dǎo)致的。
3.?? 各語言的解決方法
鉆石繼承中,父類被多次初始化是個非常難纏的問題,我們來看看其他各個語言是如何解決這個問題的:
3.1. C++
C++使用虛擬繼承來解決鉆石繼承問題。
Medium1和Medium2虛擬繼承Base。當(dāng)生成Leaf對象時,Medium1和Medium2并不會自動調(diào)用虛擬基類Base的構(gòu)造函數(shù),而需要由Leaf的構(gòu)造函數(shù)顯式調(diào)用Base的構(gòu)造函數(shù)。
3.2. Java
Java禁止使用多繼承。
Java使用單繼承+接口實現(xiàn)的方式來替代多繼承,避免了鉆石繼承產(chǎn)生的各種問題。
3.3. Ruby
Ruby禁止使用多繼承。
Ruby和Java一樣只支持單繼承,但它對多繼承的替代方式和Java不同。Ruby使用Mixin的方式來替代,在當(dāng)前類中mixin入其他模塊,來做到代碼的組裝效果。
3.4. Python
Python和C++一樣,支持多繼承的語法。但Python的解決思路和C++完全不一樣,Python使用的是super
我們把第2章的鉆石繼承用super重寫一下,看一下輸出結(jié)果
class Base(object):def __init__(self):print “Base init”class Medium1(Base):def __init__(self):super(Medium1, self).__init__()print “Medium1 init”class Medium2(Base):def __init__(self):super(Medium2, self).__init__()print “Medium2 init”class Leaf(Medium1, Medium2):def __init__(self):super(Leaf, self).__init__()print “Leaf init”我們生成Leaf對象:
>>> leaf = Leaf()
Base init
Medium2 init
Medium1 init
Leaf init
可以看到整個初始化過程符合我們的預(yù)期,Base只被初始化了1次。而且重要的是,相比原來的普通寫法,super方法并沒有寫額外的代碼,也沒有引入額外的概念
4.?? super的內(nèi)核:mro
要理解super的原理,就要先了解mro。mro是method resolution order的縮寫,表示了類繼承體系中的成員解析順序。
?
在python中,每個類都有一個mro的類方法。我們來看一下鉆石繼承中,Leaf類的mro是什么樣子的:
>>> Leaf.mro()
[<class '__main__.Leaf'>, <class '__main__.Medium1'>, <class '__main__.Medium2'>, <class '__main__.Base'>, <type 'object'>]
?
可以看到mro方法返回的是一個祖先類的列表。Leaf的每個祖先都在其中出現(xiàn)一次,這也是super在父類中查找成員的順序。?
通過mro,python巧妙地將多繼承的圖結(jié)構(gòu),轉(zhuǎn)變?yōu)閘ist的順序結(jié)構(gòu)。super在繼承體系中向上的查找過程,變成了在mro中向右的線性查找過程,任何類都只會被處理一次。
?
通過這個方法,python解決了多繼承中的2大難題:
1. 查找順序問題。從Leaf的mro順序可以看出,如果Leaf類通過super來訪問父類成員,那么Medium1的成員會在Medium2之前被首先訪問到。如果Medium1和Medium2都沒有找到,最后再到Base中查找。
2. 鉆石繼承的多次初始化問題。在mro的list中,Base類只出現(xiàn)了一次。事實上任何類都只會在mro list中出現(xiàn)一次。這就確保了super向上調(diào)用的過程中,任何祖先類的方法都只會被執(zhí)行一次。
?
至于mro的生成算法,可以參考這篇wiki:https://en.wikipedia.org/wiki/C3_linearization
5.?? super的具體用法
我們首先來看一下python中的super文檔
>>> help(super)
Help on class super in module __builtin__:
class super(object)
?|? super(type, obj) -> bound super object; requires isinstance(obj, type)
?|? super(type) -> unbound super object
?|? super(type, type2) -> bound super object; requires issubclass(type2, type)
?
光從字面來看,這可以算是python中最語焉不詳?shù)膸椭臋n之一了。甚至里面還有一些術(shù)語誤用。那super究竟應(yīng)該怎么用呢,我們重點(diǎn)來看super中的第1和第3種用法
5.1. super(type, obj)
當(dāng)我們在Leaf的__init__中寫這樣的super時:
class Leaf(Medium1, Medium2):def __init__(self):super(Leaf, self).__init__()print “Leaf init”super(Leaf, self).__init__()的意思是說:
?
從這個執(zhí)行流程可以看到,如果我們不想調(diào)用Medium1的__init__,而想要調(diào)用Medium2的__init__,那么super應(yīng)該寫成:super(Medium1, self)__init__()?
5.2. super(type, type2)
當(dāng)我們在Leaf中寫類方法的super時:
class Leaf(Medium1, Medium2):def __new__(cls):obj = super(Leaf, cls).__new__(cls)print “Leaf new”return objsuper(Leaf, cls).__new__(cls)的意思是說:
?
由于返回的是非綁定的函數(shù)對象,因此調(diào)用時不能省略函數(shù)的第一個參數(shù)。這也是這里調(diào)用__new__時,需要傳入?yún)?shù)cls的原因
同樣的,如果我們想從某個mro的某個位置開始查找,只需要修改super的第一個參數(shù)就行
6.?? 小結(jié)
至此,我們講解了和super相關(guān)的用法及原理,小結(jié)一下我們講過的內(nèi)容有:
?
標(biāo)簽:python, super, mro, 多繼承
轉(zhuǎn)載于:https://www.cnblogs.com/testview/p/4651198.html
總結(jié)
以上是生活随笔為你收集整理的深入super,看Python如何解决钻石继承难题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JS获取数组小技巧
- 下一篇: 设计模式学习笔记——目录