python---方法解析順序MRO(Method Resolution Order)<以及解決類中super方法>

MRO瞭解:

對於支持繼承的編程語言來講,其方法(屬性)可能定義在當前類,也可能來自於基類,因此在方法調用時就須要對當前類和基類進行搜索以肯定方法所在的位置。
而搜索的順序就是所謂的「方法解析順序」(Method Resolution Order,或MRO)。
對於只支持單繼承的語言來講,MRO 通常比較簡單;而對於 Python 這種支持多繼承的語言來講,MRO 就複雜不少。

而具體討論MRO,咱們須要針對不一樣python版本中的MRO進行解析

經典類:DFS深度優先搜索(Python2.2之前的版本)
新式類:BFS廣度優先搜索(Python2.2中提出,在與經典類共存的狀況下,是否繼承object是他們的區分方式)
新式類C3算法:Python2.3提出(也是如今Python3惟一支持的方式)

對於下面討論的類的多重繼承:咱們討論兩種狀況。html

一:經典類(深度優先搜索)

 在經典類中,沒有__mro__屬性能夠去查看MRO的順序,可是,可使用inspect模塊中getmro方法python

import inspect
inspect.getmro(類名)

(一)正常繼承模式

 

在正常繼承模式下,不會引發任何問題算法

(二)交叉繼承模式

 

缺點:C類本來是D的子類,如果在C中對D的某個方法進行了重載(B類沒有進行重載),那麼咱們在A中所但願使用的是C中的重載方法,編程

可是因爲查找順序是A->B->D->C,因此對於在C類中的重載方法,會在結果D時被過濾(由於在D中查找到該方法就中止了),致使永遠沒法訪問到C中的重載方法編程語言

import inspect

class D:
    def foo(self):
        print("D.foo")

class C(D):
    def foo(self):
        print("C.foo")

class B(D):
    pass

class A(B,C):
    pass

print(inspect.getmro(A))    #A->B->D->C
obj = A()
obj.foo()   #D.foo
代碼演示

二:新式類(廣度優先搜索) 

(一)正常繼承方式

缺點:B繼承於D,如果D中實現了某個方法,B能夠去調用他,可是C中也自定義了一個同名方法。那麼B會獲取到C中的方法就結束了。這可不是咱們所但願出現的ide

class E(object):
    pass

class D(object):
    def foo(self):
        print("D.foo")

class C(E):
    def foo(self):
        print("C.foo")

class B(D):
    pass

class A(B,C):
    pass

print(A.__mro__)    #A->B->C->D->E
obj = A()
obj.foo()   #C.foo
代碼演示

(二)交叉繼承方式

 

在交叉繼承的方式下,不會出現任何問題spa

三:新式類(C3算法實現:看起來就是將上面二者的優勢結合了。因此在Python3中所有都是新式類,不須要object繼承)

 (一)正常繼承方式

 (二)交叉繼承方式

 在python3中這種MRO方法是惟一使用的。.net

 推文:你真的理解Python中MRO算法嗎?3d

四:C3算法瞭解

 推文:C3算法瞭解code

 推文:C3算法瞭解(這個更加詳細)

可是上面兩個對於MRO的計算方法都有錯誤處,不過其中第二篇「C3算法瞭解」的評論給出了詳細的解法。下面我也寫出這兩篇文章中的具體解法

注意:咱們把類 C 的線性化(MRO)記爲 L[C] = [C1, C2,…,CN]。其中 C1 稱爲 L[C] 的頭其他元素 [C2,…,CN] 稱爲尾
L[object] = [object]
L[C(B1,B2,...,B(N-1),BN)] = [C] + merge(L[B1]+L[B2]+...+L[B(N-1)]+L[BN], [B1,B2,...,B(N-1),BN]) #這種解法是正確的
其餘地方能夠看上面兩篇推文便可(第二篇更加詳細)

步驟:

1.檢查第一個列表的頭元素(如 L[B1] 的頭),記做 H。

2.若 H 未出如今其它列表的尾部,則將其輸出,並將其從全部列表中刪除,而後回到步驟1;不然,取出下一個列表的頭部記做 H,繼續該步驟。(重點)
3.重複上述步驟,直至列表爲空或者不能再找出能夠輸出的元素。若是是前一種狀況,則算法結束;若是是後一種狀況,說明沒法構建繼承關係,Python 會拋出異常。

 

(一)推文一:案例推導(其中o表明object類)

解題步驟:

(二)推文二:案例推導

 

解題步驟:

可能你發現這種解法,和兩篇推文中的答案一致。可是你向下看,在super方法中提到一個錯誤案例,再去使用這種方法和推文中的方法,就知道該如何使用了。



 

下面討論__init__和super()方法的關係

一:在單繼承中super方法和__init__使用時功能上基本是無差異的

class A(object):
    def __init__(self):
        print("A.__init__.start")
        print("A.__init__.end")

class B(A):
    def __init__(self):
        print("B.__init__.start")
        super(B, self).__init__()
        print("B.__init__.end")

class C(A):
    def __init__(self):
        print("B.__init__.start")
        A.__init__(self)
        print("B.__init__.end")

b = B()
# B.__init__.start
# A.__init__.start
# A.__init__.end
# B.__init__.end

c = C()
# B.__init__.start
# A.__init__.start
# A.__init__.end
# B.__init__.end

二:super方法只在新式類中適用

>>> class A:
...     def __init__(self):
...         print("A.__init__.start")
...         print("A.__init__.end")
...
>>> class B:
...     def __init__(self):
...         print("B.__init__.start")
...         super(B, self).__init__()
...         print("B.__init__.end")
...
>>> class C(A):
...     def __init__(self):
...         print("B.__init__.start")
...         A.__init__(self)
...         print("B.__init__.end")
...
>>> b = B()
B.__init__.start
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __init__
TypeError: must be type, not classobj

三:注意super()不是父類,而是執行MRO順序中的下一個類!!

class E(object):
    def __init__(self):
        print("E")

class D(object):
    def __init__(self):
        print("D")
        super(D, self).__init__()

class C(E):
    def __init__(self):
        print("C")
        super(C, self).__init__()

class B(D):
    def __init__(self):
        print("B")
        super(B, self).__init__()

class A(B,C):
    def __init__(self):
        print("A")
        super(A, self).__init__()

print(A.__mro__) #A->B->D->C->E
obj = A()   #ABDCE

由最後輸出能夠知道,super是指向MRO順序中本身類的下一個類。

def super(class_name, self):
    mro = self.__class__.mro() #獲取mro的列表
    return mro[mro.index(class_name) + 1]  #獲取本身的索引號,去返回下一個類

四:super()能夠避免重複調用

(一)__init__方法可能致使被執行屢次

class A(object):
    def __init__(self):
        print("A.__init__")

class B(A):
    def __init__(self):
        print("B.__init__")
        A.__init__(self)

class C(B,A):
    def __init__(self):
        print("C.__init__")
        A.__init__(self)
        B.__init__(self)

c = C()
C.__init__
A.__init__
B.__init__
A.__init__  #出現重複調用

注意:

class C(A,B):  #會由於沒法建立MRO而出錯
    def __init__(self):
        print("C.__init__")
        A.__init__(self)
        B.__init__(self)

TypeError: Cannot create a consistent method resolution
order (MRO) for bases A, B

(二)使用super方法能夠避免重複調用

class A(object):
    def __init__(self):
        print("A.__init__")

class B(A):
    def __init__(self):
        print("B.__init__")
        super(B, self).__init__()

class C(B,A):
    def __init__(self):
        print("C.__init__")
        super(C, self).__init__()

c = C()
C.__init__
B.__init__
A.__init__

一樣:

class C(A,B):  #也是由於沒法生成MRO順序出錯
    def __init__(self):
        print("C.__init__")
        super(C, self).__init__()

TypeError: Cannot create a consistent method resolution
order (MRO) for bases A, B

詳細解決能夠在推文:C3算法瞭解(2)中看到。下面也會寫產生的緣由(在生成MRO順序時出錯)

正確繼承下的解法:

 

解法C3算法:

錯誤繼承的緣由:

 

C3算法解題:

推文:https://www.zhihu.com/question/20040039

相關文章
相關標籤/搜索