Python類繼承的高階特性

發表於2016-10-01

昨天在Python類的多重繼承那裡糾結了好久,在提問版塊提了個問題探討了探討(連結)才完全搞明白,現在把類的特性整理下,供以後參考

正文

首先得說明的是,Python的類分為經典類新式類
經典類是python2.2之前的東西,但是在2.7還在相容,但是在3之後的版本就只承認新式類了
新式類在python2.2之後的版本中都可以使用

經典類和新式類的區別在於:

  1. 經典類是預設沒有派生自某個基類的,而新式類是預設派生自object這個基類的:

2.經典類在類多重繼承的時候是採用從左到右深度優先原則匹配方法的..而新式類是採用C3演算法(不同於廣度優先)進行匹配的

3.經典類是沒有__MRO__instance.mro()呼叫的,而新式類是有的.

為什麼不用經典類,要更換到新式類

因為在經典類中的多重繼承會有些問題…可能導致在繼承樹中的方法查詢繞過後面的父類:

按照經典類的查詢順序從左到右深度優先的規則,在訪問d.foo1()的時候,D這個類是沒有的..那麼往上查詢,先找到B,裡面沒有,深度優先,訪問A,找到了foo1(),所以這時候呼叫的是A的foo1(),從而導致C重寫的foo1()被繞過.

所以python引入了新式類的概念,每個基類都繼承自object並且,他的匹配規則也從深度優先換到了C3


C3演算法

C3演算法是怎麼做匹配的呢..在問答版塊上面討論之後,歸結如下:

C3演算法的一個核心是merge.

在merge列表中,如果第一個序列mro的第一個類是出現在其它序列,並且也是第一個,或者不出現其它序列,那麼這個類就會從這些序列中刪除,併合到訪問順序列表中

比如:(引用問題中zhuangzebo的回答@zhuangzebo)

首先需要知道 O(object)的mro(method resolution order)列表是[O,]
那麼接下來是:

然後還有一種特殊情況:
比如:
merge(DO,CO,C) 先merge的是D
merge(DO,CO,C) 先merge的是C
意思就是.當出現有 一個類出現在兩個序列的頭(比如C) 這種情況和 這個類只有在一個序列的頭(比如D) 這種情況同時出現的時候,按照順序方式匹配。

新式類生成的訪問序列被儲存在一個叫MRO的只讀列表中..
你可以使用instance.__MRO__或者instance.mro()來訪問

最後匹配的時候就按照MRO序列的順序去匹配

C3和廣度優先的區別:

舉個例子就完全明白了:

按照廣度優先遍歷,F的MRO序列應該是[F,C,E,B,D,A]
但是C3是[F,E,D,C,B,A]
意思是你可以當做C3是在一條鏈路上深度遍歷到和另外一條鏈路的交叉點,然後去深度遍歷另外一條鏈路,最後遍歷交叉點

新式類和經典類的super按類名訪問問題

在經典類中,你如果要訪問父類的話,是用類名來訪問的..

這樣子看起來沒三問題,但是如果類的繼承結構比較複雜,會導致程式碼的可維護性很差..
所以新式類推出了super這個東西…

這時候,又有一個問題:當類是多重繼承的時候,super訪問的是哪一個類呢?
super實際上是通過__MRO__序列來確定訪問哪一個類的…實際上就是呼叫__MRO__中此類後面的一個類的方法.
比如序列為[F,E,D,C,B,A]那麼F中的super就是E,E的就是D

super按照類名訪問 混合使用帶來的坑

這時候列印出來是:

可以看出來D和A的初始化函式被亂入了兩次!
按類名訪問就相當於C語言之前的GOTO語句…亂跳,然後再用super按順序訪問..就有問題了

所以建議就是要麼一直用super,要麼一直用按照類名訪問

最佳實現:

  1. 避免多重繼承
  2. super使用一致
  3. 不要混用經典類和新式類
  4. 呼叫父類的時候注意檢查類層次

參考資料:

  1. 《python高階程式設計》
  2. http://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html
  3. http://www.cnblogs.com/i2u9/archive/2013/03/19/pythonmroc3.html

相關文章