當心掉進Python多重繼承裡的坑
關於類的知識點裡面,有一塊是關於多重繼承,跟其他主流語言一樣,Python也支援多重繼承,多重繼承雖然有一些好處,但是坑很多,我們今天就來聊一聊Python多重繼承裡面的坑.
先來看一下語法,Python多重繼承的語法很簡單:
class subClass(Base1,Base2)
這相當於你建立了一個subClass的類,讓它同時繼承Base1,Base2,一旦你在subClass的例項上有任何隱式動作,Python會回到類的層級結構中去檢查Base1,Base2,而且必須要用固定的次序去檢查,為了搞定這一點Python用了一個MRO去搜索
當然入門Python沒有好的學習資料怎麼行呢?所以小編這裡準備了一份Python學習資料,新增小編學習交流群943752371即可
要點:
多重繼承的初始化順序
菱形多重繼承的初始化順序
迷一樣的MRO
經典類和新式類的MRO區別
坑1-多重繼承內的初始化順序
先看一個簡單的例子,有一個子類subClass,繼承3個父類BaseClass,TimesTwo,PlusFive.看看這樣的結構內的類的初始化順序是怎麼樣的
>>
Init BaseClass
value is :1
Init TimesTwo
value is :2
Init PlusFive
value is :7
7
那麼我們把子類裡面多重繼承的父類的順序換一下:繼承3個父類BaseClass,PlusFive,TimesTwo.
>>
Init BaseClass
value is :1
Init TimesTwo
value is :2
Init PlusFive
value is :7
7
我們發現雖然我們多重繼承的順序是BaseClass,PlusFive,TimesTwo,但是實際執行的結果:卻還是是BaseClass,TimesTwo,PlusFive,奇怪為啥會這樣呢,我們接著往下看~~
坑2.菱形多重繼承中初始化問題
當一個子類繼承2個父類,而2個父類又都繼承一個基類,構成了一個菱形.
>>
Init BaseClass
value is :1
Init TimesTwo
value is :2
Init BaseClass
value is :1
Init PlusFive
value is :6
6
正確的結果我們是想1*2+5=7,但是實際是6,為啥呢
因為菱形繼承的問題,在呼叫第二個超類的構造器PlusFive.__init__()的時候, 它會再度去呼叫BaseClass.__init__(),從而導致self.value重新變成1.
那麼用內建函式super()是不是可以解決這個問題
>>
Init BaseClass
value is :1
Init PlusFive
value is :6
Init TimesTwo
value is :12
12
super()確實可以保證菱形頂部的公共基類的__init__()方法只會執行一次,但是執行的結果好像不是我們期望的.我們期望的是1*2+5=7,但是實際執行卻是(1+5)*2=12了,奇怪為啥會這樣呢
3.迷一樣的MRO
上面兩個問題的根源都跟MRO有關,MRO(Method Resolution Order)也叫方法解析順序,主要用於在多重繼承時判斷調的屬性的路徑(來自於哪個類)
那麼我們現在來看一下上面的例子,到底是怎麼個搜素的呢~~
我們引入Python內建模組pprint(這個Python的一個列印資料結構的模組)
我們呼叫SubClass的時候,它會呼叫TimesTwo.__init__(),而TimesTwo.__init__()又會呼叫PlusFive.__init__(),PlusFive.__init__()再去調BaseClass.__init__(),到達菱形體系的頂部
注意關鍵的地方來了,所有的初始化方法會按照跟那些__init__()相反的順序來執行.
也就是如下的初始化順序:
1)BaseClass__init__()會把value設為1
2)PlusFive.__init__()會把value加5,然後value變成6
3)TimesTwo.__init__()會把value乘以2,然後value變成12
4.經典類和新式類的MRO區別
因為Python的類中有兩種:一種經典類,一種新式類
在經典類中MRO搜尋採用簡單的從左到右的深度優先順序,而新式類是廣度優先,不信我給你舉個例子
#先看經典類
>>
I can show the information of A
return value of B
當用經典類實現的時候,我們發現呼叫的A類中的show()方法和B類的getValue()方法
#再看新式類
>>
I can show the information of C
return value of B
變成新式類的時候,結果變成了呼叫C類的show()方法和B類的getValue()方法.
差別的根源就是上面時候的經典類和新式類採用的MRO差異.
結論:
不惜一切代價地避免多重繼承,它帶來的麻煩比能解決的問題都多。如果你非要用,那你得準備好專研類的層次結構,以及花時間去找各種東西的來龍去脈吧
只有在程式碼之間有清楚的關聯,可以通過一個單獨的共性聯絡起來的時候使用繼承,或者你受現有程式碼所限非用不可的話,那也用吧
或者可以試試組合,組合則是利用模組和別的類中的函式呼叫實現了相同的目的