一、mro概述
概念
方法解析順序,是python中用于處理二義性問題的算法
二義性
問題一:有兩個基類A類和B類,A和B中都定義了f()的方法,C繼承了A和B,那麼調用C的f()方法時會出現不确定性
問題二:有一個基類A,定義了方法f(),B類和C類都繼承自A類,D類繼承了B和C類,此時出現一個問題,D類不知道繼承B的F()還是C的F()
C++解決二義性
問題一:通過同名覆寫的方法解決
問題二:通過虛繼承來解決
python解決二義性
通過C3算法避免二義性的情況
經曆過程
a、python2.2以前的版本(經典類時代)
b、python2.2版本(新式類誕生)
c、python2.3到python2.7(經典類、新式類和平發展)
d、python3至今(新式類一統江山)
示例代碼
class A(object):
def f(self):
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
複制
二、python2.2以前
經典類時代
經典類
特性:經典類是一種沒有繼承的類,對象都是type類型,如果經典類被作為父類,子類調用父類的構造函數時會出錯
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">B</span><span class="hljs-params">(A)</span>:</span>
<span class="hljs-keyword">pass</span>
複制
mro的算法為深度優先算法(DFS)
a、把根階段壓入棧結構中
b、每次從棧中彈出一個元素,搜尋所有它下一級元素,把這些元素壓入棧中。并把這個元素記為它下一個元素的前驅
c、找到所有的元素時程式結束
d、如果周遊整個樹還沒有找到,程式結束
兩種繼承模式(正常繼承模式、菱形繼承模式)
# 正常繼承模式
import inspect
class D:
pass
class E:
pass
class B(D):
pass
class C(E):
pass
class A(B, C):
pass
print(inspect.getmro(A))
# A B D C E
複制
<span class="hljs-comment"># 菱形繼承模式</span>
<span class="hljs-keyword">import</span> inspect
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">D</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">B</span><span class="hljs-params">(D)</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">C</span><span class="hljs-params">(D)</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A</span><span class="hljs-params">(B, C)</span>:</span>
<span class="hljs-keyword">pass</span>
print(inspect.getmro(A))
<span class="hljs-comment"># A B D C</span>
複制
MRO的DFS順序
兩種繼承模式在DFS下的優缺點
第一種:稱為正常繼承模式,兩個互不相關的類的多繼承,這種情況DFS順序正常,不會引起任何問題
第二種:棱形繼承模式,存在公共父類(D)的多繼承,這種情況下DFS必定經過公共父類(D),這時候想想,如果這個公共父類(D)有一些初始化屬性或者方法,但是子類(C)又重寫了這些屬性或者方法,那麼按照DFS順序必定是會先找到D的屬性或方法,那麼C的屬性或者方法将永遠通路不到,導緻C隻能繼承無法重寫(override)。這也就是為什麼新式類不使用DFS的原因,因為他們都有一個公共的祖先object
三、python2.2
新式類誕生
新式類
特性:為了使類和内置的類型更加統一,引入新式類。新式類的每個類都繼承于一個基類,可以是自定義的類或者其他類,預設是object,子類可以調用父類的構造函數
兩種MRO算法
- 如果是經典類使用DFS
-
如果是新式類使用BSF(廣度優先算法)
a、把根階段放到隊列隊尾
b、每次從隊列的頭部取一個元素,搜尋所有它下一級元素,把這些元素放到隊列的末尾。并把這個元素記為它下一個元素的前驅
c、找到所有的元素時程式結束
d、如果周遊整個樹還沒有找到,程式結束
兩種繼承模式(正常繼承模式、菱形繼承模式)
# 正常繼承模式
class D(object):
pass
class E:
pass
class B(D):
pass
class C(E):
pass
class A(B, C):
pass
print(A.__mro__)
# A B C D E
複制
<span class="hljs-comment"># 菱形繼承模式</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">D</span><span class="hljs-params">(objcet)</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">B</span><span class="hljs-params">(D)</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">C</span><span class="hljs-params">(D)</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A</span><span class="hljs-params">(B, C)</span>:</span>
<span class="hljs-keyword">pass</span>
print(A.__mro__)
<span class="hljs-comment"># A B C D</span>
複制
MRO的BFS順序
兩種繼承模式在BFS下的優缺點
第一種:正常繼承模式,看起來正常,但實際上感覺别捏,比如B繼承D的f()函數,恰巧C中也實作了f()函數,那麼BFS順序先通路B在去通路C,f()函數會選擇C的,這種應該先從B和B的父類開始找才是正确的順序,稱為單調性
第二種:菱形內建模式,在BFS模式下解決了DFS查找順序的問題,但是它也違背了單調性
四、python2.3到python2.7
經典類與新式類和平發展
在之前的BFS算法存在很大的問題,從python2.3開始新式類的MRO算法使用C3算法,C3算法解決了單調性問題和隻能繼承不能重寫的問題
五、python3時代
新式類一統江山
C3算法
C3算法解決了單調性問題和隻能繼承無法重寫問題
兩種繼承模式(正常繼承模式、菱形繼承模式)
# 正常繼承模式
class D(object):
pass
class E:
pass
class B(D):
pass
class C(E):
pass
class A(B, C):
pass
print(A.__mro__)
複制
<span class="hljs-comment"># 菱形繼承模式</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">D</span><span class="hljs-params">(objcet)</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">B</span><span class="hljs-params">(D)</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">C</span><span class="hljs-params">(D)</span>:</span>
<span class="hljs-keyword">pass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A</span><span class="hljs-params">(B, C)</span>:</span>
<span class="hljs-keyword">pass</span>
print(A.__mro__)
複制
MRO的C3順序
拓撲排序
對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是将G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u線上性序列中出現在v之前。通常,這樣的線性序列稱為滿足拓撲次序(Topological Order)的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序得到該集合上的一個全序,這個操作稱之為拓撲排序
模拟拓撲排序
class D(object):
pass
class E(object):
pass
class F(object):
pass
class B(E, D):
pass
class C(D, F):
pass
class A(B, C):
pass
print(A.__mro__)
# A B E C D F object
複制
首先找到入讀點為0的點,隻有一個A,把A拿出來,把A相關的邊裁剪掉,再找入讀點為0的點,有兩個(B、C)。根據最左側原則,拿B,此時的順序AB,把B相關的邊裁剪掉。此時入讀點為0的點有E和C,取最左側是E,此時的順序為ABE。裁剪掉E的相關邊,此時隻有一個入讀點為0的點為C,取C,此時的順序是ABEC。裁剪掉C的相關邊,此時入讀點為0的點有D和F,取左側的D點,此時的順序為ABECD,裁剪掉D的相關邊,此時隻有F的入讀點為0,取F,此時的順序ABECDF,裁剪掉F的相關邊,此時隻有object入讀點為0,取object,此時順序為ABECDFobject