1. 程式人生 > >當心掉進Python多重繼承裡的坑

當心掉進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差異.

結論:

不惜一切代價地避免多重繼承,它帶來的麻煩比能解決的問題都多。如果你非要用,那你得準備好專研類的層次結構,以及花時間去找各種東西的來龍去脈吧

只有在程式碼之間有清楚的關聯,可以通過一個單獨的共性聯絡起來的時候使用繼承,或者你受現有程式碼所限非用不可的話,那也用吧

或者可以試試組合,組合則是利用模組和別的類中的函式呼叫實現了相同的目的