1. Python的繼承以及調用父類成員
python子類調用父類成員有2種方法,分別是普通方法和super方法python
假設Base是基類算法
class Base(object): def __init__(self): print 「Base init」
則普通方法以下函數
class Leaf(Base): def __init__(self): Base.__init__(self) print 「Leaf init」
super方法以下post
class Leaf(Base): def __init__(self): super(Leaf, self).__init__() print 「Leaf init」
在上面的簡單場景下,兩種方法的效果一致:ui
>>> leaf = Leaf()this
Base initspa
Leaf initcode
2. 鑽石繼承遇到的難題
當咱們來到鑽石繼承場景時,咱們就遇到了一個難題:對象
若是咱們仍是使用普通方法調用父類成員,代碼以下:blog
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」
當咱們生成Leaf對象時,結果以下:
>>> leaf = Leaf()
Base init
Medium1 init
Base init
Medium2 init
Leaf init
能夠看到Base被初始化了兩次!這是因爲Medium1和Medium2各自調用了Base的初始化函數致使的。
3. 各語言的解決方法
鑽石繼承中,父類被屢次初始化是個很是難纏的問題,咱們來看看其餘各個語言是如何解決這個問題的:
3.1. C++
C++使用虛擬繼承來解決鑽石繼承問題。
Medium1和Medium2虛擬繼承Base。當生成Leaf對象時,Medium1和Medium2並不會自動調用虛擬基類Base的構造函數,而須要由Leaf的構造函數顯式調用Base的構造函數。
3.2. Java
Java禁止使用多繼承。
Java使用單繼承+接口實現的方式來替代多繼承,避免了鑽石繼承產生的各類問題。
3.3. Ruby
Ruby禁止使用多繼承。
Ruby和Java同樣只支持單繼承,但它對多繼承的替代方式和Java不一樣。Ruby使用Mixin的方式來替代,在當前類中mixin入其餘模塊,來作到代碼的組裝效果。
3.4. Python
Python和C++同樣,支持多繼承的語法。但Python的解決思路和C++徹底不同,Python使用的是super
咱們把第2章的鑽石繼承用super重寫一下,看一下輸出結果
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
能夠看到整個初始化過程符合咱們的預期,Base只被初始化了1次。並且重要的是,相比原來的普通寫法,super方法並無寫額外的代碼,也沒有引入額外的概念
4. super的內核: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的每一個祖先都在其中出現一次,這也是super在父類中查找成員的順序。
經過mro,python巧妙地將多繼承的圖結構,轉變爲list的順序結構。super在繼承體系中向上的查找過程,變成了在mro中向右的線性查找過程,任何類都只會被處理一次。
經過這個方法,python解決了多繼承中的2大難題:
1. 查找順序問題。從Leaf的mro順序能夠看出,若是Leaf類經過super來訪問父類成員,那麼Medium1的成員會在Medium2以前被首先訪問到。若是Medium1和Medium2都沒有找到,最後再到Base中查找。
2. 鑽石繼承的屢次初始化問題。在mro的list中,Base類只出現了一次。事實上任何類都只會在mro list中出現一次。這就確保了super向上調用的過程當中,任何祖先類的方法都只會被執行一次。
至於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中最語焉不詳的幫助文檔之一了。甚至裏面還有一些術語誤用。那super究竟應該怎麼用呢,咱們重點來看super中的第1和第3種用法
5.1. super(type, obj)
當咱們在Leaf的__init__中寫這樣的super時:
class Leaf(Medium1, Medium2): def __init__(self): super(Leaf, self).__init__() print 「Leaf init」
super(Leaf, self).__init__()的意思是說:
- 獲取self所屬類的mro, 也就是[Leaf, Medium1, Medium2, Base]
- 從mro中Leaf右邊的一個類開始,依次尋找__init__函數。這裏是從Medium1開始尋找
- 一旦找到,就把找到的__init__函數綁定到self對象,並返回
從這個執行流程能夠看到,若是咱們不想調用Medium1的__init__,而想要調用Medium2的__init__,那麼super應該寫成:super(Medium1, self)__init__()
5.2. super(type, type2)
當咱們在Leaf中寫類方法的super時:
class Leaf(Medium1, Medium2): def __new__(cls): obj = super(Leaf, cls).__new__(cls) print 「Leaf new」 return obj
super(Leaf, cls).__new__(cls)的意思是說:
- 獲取cls這個類的mro,這裏也是[Leaf, Medium1, Medium2, Base]
- 從mro中Leaf右邊的一個類開始,依次尋找__new__函數
- 一旦找到,就返回「非綁定」的__new__函數
因爲返回的是非綁定的函數對象,所以調用時不能省略函數的第一個參數。這也是這裏調用__new__時,須要傳入參數cls的緣由
一樣的,若是咱們想從某個mro的某個位置開始查找,只須要修改super的第一個參數就行
6. 小結
至此,咱們講解了和super相關的用法及原理,小結一下咱們講過的內容有:
- python調用父類成員共有2種方法:普通方法,super方法
- 在鑽石繼承中,普通方法會遇到Base類兩次初始化的問題
- 簡述了其餘語言對這個問題的解決方法,並用實例展現了python使用super能夠解決此問題
- 在講super具體用法前,先講了super的內核:mro的知識和原理
- 講解了super兩種主要的用法及原理