溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解C3線性化算法與MRO之Python中的多繼承

發(fā)布時間:2021-10-08 09:40:59 來源:億速云 閱讀:92 作者:iii 欄目:開發(fā)技術

這篇文章主要講解了“如何理解C3線性化算法與MRO之Python中的多繼承”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何理解C3線性化算法與MRO之Python中的多繼承”吧!

目錄
  • 什么是 MRO

  • New-style Class vs. Old-style Class

  • 理解 old-style class 的 MRO

  • 理解 new-style class 的 MRO

  • C3線性化算法

Python 中的方法解析順序(Method Resolution Order, MRO)定義了多繼承存在時 Python 解釋器查找函數(shù)解析的正確方式。當 Python 版本從 2.2 發(fā)展到 2.3 再到現(xiàn)在的 Python 3,MRO算法也隨之發(fā)生了相應的變化。這種變化在很多時候影響了我們使用不同版本 Python 編程的過程。

什么是 MRO

MRO 全稱方法解析順序(Method Resolution Order)。它定義了 Python 中多繼承存在的情況下,解釋器查找函數(shù)解析的具體順序。什么是函數(shù)解析順序?我們首先用一個簡單的例子來說明。請仔細看下面代碼:

class A():
    def who_am_i(self):
        print("I am A")
        
class B(A):
    pass
        
class C(A):
    def who_am_i(self):
        print("I am C")

class D(B,C):
    pass
    
d = D()

如果我問在 Python 2 中使用 D 的實例調用 d.who_am_i(),究竟執(zhí)行的是 A 中的 who_am_i() 還是 C 中的 who_am_i(),我想百分之九十以上的人都會不假思索地回答:肯定是 C 中的 who_am_i(),因為 C 是 D 的直接父類。然而,如果你把代碼用 Python 2 運行一下就可以看到 d.who_am_i() 打印的是 I am A。

是不是覺得很混亂很奇怪?感到奇怪就對了?。。?/p>

undefined

這個例子充分展示了 MRO 的作用:決定基類中的函數(shù)到底應該以什么樣的順序調用父類中的函數(shù)??梢悦鞔_地說,Python 發(fā)展到現(xiàn)在,MRO 算法已經不是一個憑借著執(zhí)行結果就能猜出來的算法了。如果沒有深入到 MRO 算法的細節(jié),稍微復雜一點的繼承關系和方法調用都能徹底繞暈你。

New-style Class vs. Old-style Class

在介紹不同版本的 MRO 算法之前,我們有必要簡單地回顧一下 Python 中類定義方式的發(fā)展歷史。盡管在 Python 3 中已經廢除了老式的類定義方式和 MRO 算法,但對于仍然廣泛使用的 Python 2 來說,不同的類定義方式與 MRO 算法之間具有緊密的聯(lián)系。了解這一點將幫助我們從 Python 2 向 Python 3 遷移時不會出現(xiàn)莫名其妙的錯誤。

在 Python 2.1 及以前,我們定義一個類的時候往往是這個樣子(我們把這種類稱為 old-style class):

class A:
    def __init__(self):
        pass

Python 2.2 引入了新的模型對象(new-style class),其建議新的類型通過如下方式定義:

class A(object):
    def __init__(self):
        pass

注意后一種定義方式顯示注明類 A 繼承自 object。Python 2.3 及后續(xù)版本為了保持向下兼容,同時提供以上兩種類定義用以區(qū)分 old-style class 和 new-style class。Python 3 則完全廢棄了 old-style class 的概念,不論你通過以上哪種方式書寫代碼,Python 3 都將明確認為類 A 繼承自 object。這里我們只是引入 old-style 和 new-style 的概念,如果你對他們的區(qū)別感興趣,可以自行看 stackoverflow 上有關該問題的解釋。

理解 old-style class 的 MRO

我們使用前文中的類繼承關系來介紹 Python 2 中針對 old-style class 的 MRO 算法。如果你在前面執(zhí)行過那段代碼,你可以看到調用 d.who_am_i() 打印的應該是 I am A。為什么 Python 2 的解釋器在確定 D 中的函數(shù)調用時要先搜索 A 而不是先搜索 D 的直接父類 C 呢?

這是由于 Python 2 對于 old-style class 使用了非常簡單的基于深度優(yōu)先遍歷的 MRO 算法(關于深度優(yōu)先遍歷,我想大家肯定都不陌生)。當一個類繼承自多個類時,Python 2 按照從左到右的順序深度遍歷類的繼承圖,從而確定類中函數(shù)的調用順序。這個過程具體如下:

  • 檢查當前的類里面是否有該函數(shù),如果有則直接調用。

  • 檢查當前類的第一個父類里面是否有該函數(shù),如果沒有則檢查父類的第一個父類是否有該函數(shù),以此遞歸深度遍歷。

  • 如果沒有則回溯一層,檢查下一個父類里面是否有該函數(shù)并按照 2 中的方式遞歸。

上面的過程與標準的深度優(yōu)先遍歷只有一點細微的差別:步驟 2 總是按照繼承列表中類的先后順序來選擇分支的遍歷順序。具體來說,類 D 的繼承列表中類順序為 B, C,因此,類 D 按照先遍歷 B 分支再遍歷 C 分支的順序來確定 MRO。

我們繼續(xù)用第一個例子中的函數(shù)繼承圖來說明這個過程:

如何理解C3線性化算法與MRO之Python中的多繼承

按照上述深度遞歸的方式,函數(shù) d.who_am_i() 調用的搜索順序是 D, B, A, C, A。由于一個類不能兩次出現(xiàn),因此在搜索路徑中去除掉重復出現(xiàn)的 A,得到最終的方法解析順序是 D, B, A, C。這樣一來你就明白了為什么 d.who_am_i() 打印的是 I am A 了。

在 Python 2 中,我們可以通過如下方式來查看 old-style class 的 MRO:

>>> import inspect
>>> inspect.getmro(D)

理解 new-style class 的 MRO

從上面的結果可以看到,使用深度優(yōu)先遍歷的查找算法并不合理。因此,Python 3 以及 Python 2 針對 new-style class 采用了新的 MRO 算法。如果你使用 Python 3 重新運行一遍上述腳本,你就可以看到函數(shù) d.who_am_i() 的打印結果是 I am C

>>> d.who_am_i()
I am C
>>> D.__mro__
(<class 'test.D'>, <class 'test.B'>, <class 'test.C'>, <class 'test.A'>, <class 'object'>)

新算法與基于深度遍歷的算法類似,但是不同在于新算法會對深度優(yōu)先遍歷得到的搜索路徑進行額外的檢查。其從左到右掃描得到的搜索路徑,對于每一個節(jié)點解釋器都會判斷該節(jié)點是不是好的節(jié)點。如果不是好的節(jié)點,那么將其從當前的搜索路徑中移除。

那么問題在于,什么是一個好的節(jié)點?我們說 N 是一個好的節(jié)點當且僅當搜索路徑中 N 之后的節(jié)點都不繼承自 N。我們還以上述的類繼承圖為例,按照深度優(yōu)先遍歷得到類 D 中函數(shù)的搜索路徑 D, B, A, C, A。之后 Python 解釋器從左向右檢查時發(fā)現(xiàn)第三個節(jié)點 A 不是一個好的節(jié)點,因為 A 之后的節(jié)點 C 繼承自 A。因此其將 A 從搜索路徑中移除,然后得到最后的調用順序 D, B, C, A。

采用上述算法,D 中的函數(shù)調用將優(yōu)先查找其直接父類 B 和 C 中的相應函數(shù)。

C3線性化算法

上一小結我們從直觀上概述了針對 new-style class 的 MRO 算法過程。事實上這個算法有一個明確的名字 C3 linearization。下面我們給出其形式化的計算過程。

如何理解C3線性化算法與MRO之Python中的多繼承

上面的過程看起來好像很復雜,我們用一個例子來具體執(zhí)行一下,你就會覺得其實還是挺簡單的。假設我們有如下的一個類繼承關系:

class X():
    def who_am_i(self):
        print("I am a X")
        
class Y():
    def who_am_i(self):
        print("I am a Y")
        
class A(X, Y):
    def who_am_i(self):
        print("I am a A")
        
class B(Y, X):
     def who_am_i(self):
         print("I am a B")
         
class F(A, B):
    def who_am_i(self):
        print("I am a F")

如何理解C3線性化算法與MRO之Python中的多繼承

Traceback (most recent call last):
  File "test.py", line 17, in <module>
    class F(A, B):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases X, Y

感謝各位的閱讀,以上就是“如何理解C3線性化算法與MRO之Python中的多繼承”的內容了,經過本文的學習后,相信大家對如何理解C3線性化算法與MRO之Python中的多繼承這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI