深入super,看Python如何解決鑽石繼承難題

來源:互聯網
上載者:User

標籤:

1.   Python的繼承以及調用父類成員

python子類調用父類成員有2種方法,分別是普通方法和super方法

假設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.   鑽石繼承遇到的難題

當我們來到鑽石繼承情境時,我們就遇到了一個難題:

如果我們還是使用普通方法調用父類成員,代碼如下:

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__()的意思是說:

  1. 擷取self所屬類的mro, 也就是[Leaf, Medium1, Medium2, Base]
  2. 從mro中Leaf右邊的一個類開始,依次尋找__init__函數。這裡是從Medium1開始尋找
  3. 一旦找到,就把找到的__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)的意思是說:

  1. 擷取cls這個類的mro,這裡也是[Leaf, Medium1, Medium2, Base]
  2. 從mro中Leaf右邊的一個類開始,依次尋找__new__函數
  3. 一旦找到,就返回“非綁定”的__new__函數

 

由於返回的是非綁定的函數對象,因此調用時不能省略函數的第一個參數。這也是這裡調用__new__時,需要傳入參數cls的原因

同樣的,如果我們想從某個mro的某個位置開始尋找,只需要修改super的第一個參數就行

6.   小結

至此,我們講解了和super相關的用法及原理,小結一下我們講過的內容有:

  1. python調用父類成員共有2種方法:普通方法,super方法
  2. 在鑽石繼承中,普通方法會遇到Base類兩次初始化的問題
  3. 簡述了其他語言對這個問題的解決方案,並用執行個體展示了python使用super可以解決此問題
  4. 在講super具體用法前,先講了super的核心:mro的知識和原理
  5. 講解了super兩種主要的用法及原理

 

標籤:python, super, mro, 多繼承

深入super,看Python如何解決鑽石繼承難題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.