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唯一支持的方式)

對於下面討論的類的多重繼承:我們討論兩種情況。

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

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

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中的方法就結束了。這可不是我們所希望出現的

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
代碼演示

(二)交叉繼承方式

 

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

三:新式類(C3算法實現:看起來就是將上面兩者的優點結合了。所以在Python3中全部都是新式類,不需要object繼承)

 (一)正常繼承方式

 (二)交叉繼承方式

 在python3中這種MRO方法是唯一使用的。

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

四:C3算法了解

 推文:C3算法了解

 推文: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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM