天天看點

面向對象-mro

一、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順序

面向對象-mro

兩種繼承模式在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順序

面向對象-mro

兩種繼承模式在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順序

面向對象-mro

拓撲排序

對一個有向無環圖(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           

複制

面向對象-mro

首先找到入讀點為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