Python 2.3 方法解析順序
***********************

備註:

  這是一份歷史文件，作為正式文件的附錄提供。此處討論的方法解析順序
  (Method Resolution Order) 是在 Python 2.3 中 *引入* 的，但仍在後續版
  本中使用，包括 Python 3。

作者：Michele Simionato。

摘要:
   *此文件適用於想要了解 Python 2.3 中使用的 C3 方法解析順序的 Python
   程式設計師。雖然它不是為初學者準備的，但透過許多實際範例進行教學。
   我沒找到其他具有相同範圍的公開文件，因此這應該很有用。*

免責聲明：

   *我根據 Python 2.3 授權條款將此文件捐贈給 Python 軟體基金會。如同往
   常，我警告讀者，以下內容 *應該* 是正確的，但我不提供任何保證。使用
   時風險自負！*

致謝：

   *感謝 Python 郵件列表中所有給予我支持的人。Paul Foley 指出了各種不
   精確之處，並促使我加入了區域優先順序（local precedence ordering）的
   部分。David Goodger 協助 reStructuredText 的格式化。David Mertz 協
   助編輯。最後，Guido van Rossum 熱情地將此文件加入到 Python 2.3 官方
   首頁。*


開端
====

   *Felix qui potuit rerum cognoscere causas* -- Virgilius

一切始於 Samuele Pedroni 向 Python 開發郵件列表發表的貼文 [1]。在他的
貼文中，Samuele 指出 Python 2.2 的方法解析順序不具單調性（monotonic）
，並提議以 C3 方法解析順序來取代。Guido 同意他的論點，因此 Python 2.3
現在使用 C3。C3 方法本身與 Python 無關，因為它是由研究 Dylan 語言的人
所發明，並在一篇針對 Lisp 程式設計師的論文 [2] 中描述。本文為想要了解
此變更原因的 Python 程式設計師提供了 C3 演算法（希望是）易讀的討論。

首先讓我指出，我要說的僅適用於 Python 2.2 中引入的 *新式類別（new
style classes）*：*經典類別（classic classes）* 維持其舊有的方法解析順
序，即深度優先然後由左至右。因此，經典類別的舊程式碼不會受到影響；即使
原則上 Python 2.2 新式類別的程式碼可能會受影響，但實際上 C3 解析順序與
Python 2.2 方法解析順序不同的情況極為罕見，因此預期不會真正破壞程式碼
。因此：

   *別怕！*

此外，除非你大量使用多重繼承且有複雜的類別階層，否則你無需了解 C3 演算
法，可以輕鬆跳過本文。另一方面，如果你真的想知道多重繼承如何運作，那麼
本文就是為你準備的。好消息是，事情並不像你想像的那樣複雜。

讓我從一些基本定義開始。

1. 給定複雜多重繼承階層中的類別 C，要指定方法被覆寫（override）的順序
   並非易事，也就是要指定 C 的祖先順序。

2. 類別 C 的祖先串列（包含類別本身），從最近的祖先到最遠的祖先排序，稱
   為類別優先串列（class precedence list）或 C 的*線性化（
   linearization）*。

3. *方法解析順序 (Method Resolution Order, MRO)* 是建構線性化的一組規
   則。在 Python 文獻中，習慣用語「C 的 MRO」也是 C 類別線性化的同義詞
   。

4. 例如，在單一繼承階層的情況下，如果 C 是 C1 的子類別，而 C1 是 C2 的
   子類別，那麼 C 的線性化就是串列 [C, C1, C2]。然而，在多重繼承階層中
   ，線性化的建構更加複雜，因為要建構一個遵守*區域優先順序（local
   precedence ordering）* 和*單調性（monotonicity）* 的線性化更加困難
   。

5. 我將在稍後討論區域優先順序，但可以在此給出單調性的定義。當以下條件
   為真時，MRO 具有單調性：*如果 C1 在 C 的線性化中先於 C2，那麼 C1 在
   C 的任何子類別的線性化中也先於 C2*。否則，衍生新類別這個看似無害的
   操作可能會改變方法的解析順序，進而引入非常微妙的錯誤。稍後將展示發
   生這種情況的範例。

6. 並非所有類別都能進行線性化。在複雜的階層結構中，有些情況下無法衍生
   出一個類別，使其線性化遵守所有所需的屬性。

以下是這種情況的範例。考慮以下階層結構

>>> O = object
>>> class X(O): pass
>>> class Y(O): pass
>>> class A(X,Y): pass
>>> class B(Y,X): pass

可以用以下繼承圖來表示，其中我用 O 標示 "object" 類別，這是新式類別的
任何階層結構的起點：

       -----------
      |           |
      |    O      |
      |  /   \    |
       - X    Y  /
         |  / | /
         | /  |/
         A    B
         \   /
           ?

在這種情況下，不可能從 A 和 B 衍生出新的類別 C，因為 X 在 A 中先於 Y，
但 Y 在 B 中先於 X，因此 C 的方法解析順序會產生歧義。

Python 2.3 在這種情況下會引發例外（TypeError: MRO conflict among bases
Y, X），防止程式設計師建立有歧義的階層結構。Python 2.2 則不會引發例外
，而是選擇 *ad hoc* 順序（在這種情況下為 CABXYO）。


C3 方法解析順序
===============

讓我介紹一些簡單的符號標示法，這對以下討論很有用。我將使用簡寫符號：

   C1 C2 ... CN

用來表示類別串列 [C1, C2, ... , CN]。

串列的 *head*（頭部）是其第一個元素：

   head = C1

而 *tail*（尾部）是串列的其餘部分：

   tail = C2 ... CN.

我還將使用以下符號：

   C + (C1 C2 ... CN) = C C1 C2 ... CN

標示串列的和 [C] + [C1, C2, ..., CN]。

現在我就可以繼續解釋 MRO 在 Python 2.3 中的運作方式。

考慮多重繼承階層結構中的類別 C，C 從基底類別 B1、B2、...、BN 繼承。我
們想計算類別 C 的線性化 L[C]。規則如下：

   *C 的線性化是 C 加上父類別線性化的合併以及父類別串列的和。*

用符號標示：

   L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)

特別是如果 C 是沒有父類別的 "object" 類別，那麼線性化是簡單的：

   L[object] = object.

然而，一般來說必須根據以下規則來計算合併：

   *取第一個串列的頭部，即 L[B1][0]；如果此頭部不在其他任何串列的尾部
   中，那麼將它加入到 C 的線性化中，並從合併中的所有串列移除它；否則查
   看下一個串列的頭部，如果它是一個好的頭部就取它。然後重複此操作，直
   到所有類別都被移除或無法找到好的頭部。在後面這種情況下，無法建構合
   併，Python 2.3 將拒絕建立類別 C 並引發例外。*

此規則確保合併操作*保留*順序（如果順序可以被保留）。另一方面，如果無法
保留順序（如上面討論的嚴重順序分歧的範例），則無法計算合併。

如果 C 只有一個父類別（單一繼承），則合併的計算是微不足道的。在這種情
況下：

   L[C(B)] = C + merge(L[B],B) = C + L[B]

但是，在多重繼承的情況下，事情更加複雜，我不指望你能在沒有幾個範例的情
況下理解這個規則 ;-)


範例
====

第一個例子，請參考以下階層結構：

>>> O = object
>>> class F(O): pass
>>> class E(O): pass
>>> class D(O): pass
>>> class C(D,F): pass
>>> class B(D,E): pass
>>> class A(B,C): pass

在這種情況下，繼承圖可以繪製為：

                                6
                               ---
      Level 3                 | O |                  （更廣泛）
                            /  ---  \
                           /    |    \                      |
                          /     |     \                     |
                         /      |      \                    |
                        ---    ---    ---                   |
      Level 2        3 | D | 4| E |  | F | 5                |
                        ---    ---    ---                   |
                         \  \ _ /       |                   |
                          \    / \ _    |                   |
                           \  /      \  |                   |
                            ---      ---                    |
      Level 1            1 | B |    | C | 2                 |
                            ---      ---                    |
                              \      /                      |
                               \    /                      \ /
                                 ---
      Level 0                 0 | A |                （更專精）
                                 ---

O、D、E 和 F 的線性化很簡單：

   L[O] = O
   L[D] = D O
   L[E] = E O
   L[F] = F O

B 的線性化可以計算為：

   L[B] = B + merge(DO, EO, DE)

我們看到 D 是一個好的頭部，因此我們取它，並將其簡化為 "merge(O,EO,E)"
。現在 O 不是一個好的頭部，因為它位於序列 EO 的尾部。在這種情況下，規
則說我們必須跳到下一個序列。然後我們看到 E 是一個好的頭部；我們取它，
並簡化為計算 "merge(O, O)" 得出 O。因此：

   L[B] =  B D E O

使用相同的程序可以發現：

   L[C] = C + merge(DO,FO,DF)
        = C + D + merge(O,FO,F)
        = C + D + F + merge(O,O)
        = C D F O

現在我們可以計算出：

   L[A] = A + merge(BDEO,CDFO,BC)
        = A + B + merge(DEO,CDFO,C)
        = A + B + C + merge(DEO,DFO)
        = A + B + C + D + merge(EO,FO)
        = A + B + C + D + E + merge(O,FO)
        = A + B + C + D + E + F + merge(O,O)
        = A B C D E F O

在此範例中，線性化是根據繼承層級以相當不錯的方式排序的，因為較低層級（
即更專精的類別）具有更高的優先級（請參見繼承圖）。但是這不是一般情況。

第二個範例的線性化之計算我留給讀者當作練習：

>>> O = object
>>> class F(O): pass
>>> class E(O): pass
>>> class D(O): pass
>>> class C(D,F): pass
>>> class B(E,D): pass
>>> class A(B,C): pass

與上一個範例的唯一區別是 B(D,E) --> B(E,D) 的變化；但是即使是這樣小小
的修改，也完全改變了階層結構的順序：

                                 6
                                ---
      Level 3                  | O |
                             /  ---  \
                            /    |    \
                           /     |     \
                          /      |      \
                        ---     ---    ---
      Level 2        2 | E | 4 | D |  | F | 5
                        ---     ---    ---
                         \      / \     /
                          \    /   \   /
                           \  /     \ /
                            ---     ---
      Level 1            1 | B |   | C | 3
                            ---     ---
                             \       /
                              \     /
                                ---
      Level 0                0 | A |
                                ---

請注意，階層結構的第二層中的類別 E，先於階層結構第一層的類別 C，即 E
比 C 更專精，即使它處於更高層。

懶惰的程式設計師可以直接從 Python 2.2 取得 MRO，因為在這種情況下，它與
Python 2.3 線性化一致。呼叫類別 A 的 "mro()" 方法就足夠了：

>>> A.mro()
[<class 'A'>, <class 'B'>, <class 'E'>,
<class 'C'>, <class 'D'>, <class 'F'>,
<class 'object'>]

最後，讓我考慮第一部分中討論的範例，涉及嚴重的順序分歧。在這種情況下，
計算 O、X、Y、A 和 B 的線性化是很簡單的：

      L[O] = 0
      L[X] = X O
      L[Y] = Y O
      L[A] = A X Y O
      L[B] = B Y X O

但是，我們不可能計算出從 A 和 B 繼承的類別 C 的線性化：

   L[C] = C + merge(AXYO, BYXO, AB)
        = C + A + merge(XYO, BYXO, B)
        = C + A + B + merge(XYO, YXO)

在這一點上，我們無法合併串列 XYO 和 YXO，因為 X 位於 YXO 的尾部，而 Y
則位於 XYO 的尾部：因此，沒有好的頭部，C3 演算法停止。Python 2.3 會引
發錯誤而拒絕建立類別 C。


不良的方法解析順序
==================

當 MRO 打破諸如區域優先順序和單調性之類的基本屬性時，MRO 是*不良*的。
在本節中，我將證明經典類別的 MRO 和 Python 2.2 的新式類別的 MRO 都是不
好的。

從區域優先順序開始更容易。考慮以下範例：

>>> F=type('Food',(),{'remember2buy':'spam'})
>>> E=type('Eggs',(F,),{'remember2buy':'eggs'})
>>> G=type('GoodFood',(F,E),{}) # under Python 2.3 this is an error!

包含繼承圖

                   O
                   |
      (buy spam)   F
                   | \
                   | E   (buy eggs)
                   | /
                   G

            (buy eggs or spam ?)

我們看到類別 G 從 F 和 E 繼承，F 在 E *之前*：因此，我們希望屬性
*G.remember2buy* 被 *F.remember2buy* 繼承，而不是 *E.remember2buy*：儘
管如此，Python 2.2 給出

>>> G.remember2buy
'eggs'

這是區域優先順序的破壞，因為區域優先串列中的順序，即 G 的父類別串列，
在 Python 2.2 的 G 的線性化中不被保留：

   L[G,P22]= G E F object   # F *跟隨* E

有人可能會說，F 在 Python 2.2 線性化中跟隨 E 的原因是 F 比 E 特化程度
較低，因為 F 是 E 的超類別；然而，局部優先順序的破壞非常不直覺且容易出
錯。尤其如此，因為它與舊式類別不同：

>>> class F: remember2buy='spam'
>>> class E(F): remember2buy='eggs'
>>> class G(F,E): pass
>>> G.remember2buy
'spam'

在這種情況下，MRO 是 GFEF 並且保留區域優先順序。

通常應避免諸如上一個等階層結構，因為尚不清楚 F 是否應覆蓋 E 或反過來。
Python 2.3 透過在建立類別 G 時引發例外來解決歧義，從而有效地阻止程式設
計師產生模棱兩可的階層結構。原因是當這樣合併時 C3 演算法會失敗：

   merge(FO,EFO,FE)

無法計算，因為 F 在 EFO 的尾部，而 E 在 FE 的尾部。

真正的解決方案是設計一個非歧義的階層結構，即源自 E 和 F（更具體的第一
）而不是 F 和 E；在這種情況下，MRO 毫無疑問是 GEF。

                 O
                 |
                 F (spam)
               / |
      (eggs)   E |
               \ |
                 G
                   (eggs, no doubt)

Python 2.3 迫使程式設計師要編寫良好（或至少較不易於出錯）的階層結構。

與之相關的是，我指出 Python 2.3 演算法足夠聰明，可以識別出明顯的錯誤，
例如父類別串列中類別的重複：

>>> class A(object): pass
>>> class C(A,A): pass # error
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: duplicate base class A

在這種情況下，Python 2.2（無論是經典類別還是新式類別）不會引發任何例外
。

最後，我想指出我們從這個範例中學到的兩個教訓：

1. 儘管名稱如此，但不僅是方法的解析順序，MRO 也決定了屬性的解析順序；

2. Pythonistas 的預設食物是 spam！（但是你已經知道 ;-)

在討論了區域優先順序的問題之後，現在讓我考慮單調性問題。我的目標是表明
經典類別的 MRO 或 Python 2.2 新式類別都不是單調的。

為了證明經典類別的 MRO 是非單調的，查看鑽石圖就足夠了：

         C
        / \
       /   \
      A     B
       \   /
        \ /
         D

可以很容易地辨別出這種不一致：

   L[B,P21] = B C        # B 優先於 C：B 的方法勝出
   L[D,P21] = D A C B C  # B 跟隨 C：C 的方法勝出！

另一方面，Python 2.2 和 2.3 MRO 沒有問題，它們兩個都給出了：

   L[D] = D A B C

Guido 在他的文章 [3] 中指出，經典的 MRO 在實踐中還不錯，因為通常可以避
開經典類別的鑽石圖。但是所有新式類別都從 "object" 繼承，因此鑽石圖是不
可避免的，並且在每個多重繼承圖中都出現了不一致之處。

Python 2.2 的 MRO 讓打破單調性變得困難，但並非不可能。以下最初由
Samuele Pedroni 提供的範例展示 Python 2.2 的 MRO 是非單調的：

>>> class A(object): pass
>>> class B(object): pass
>>> class C(object): pass
>>> class D(object): pass
>>> class E(object): pass
>>> class K1(A,B,C): pass
>>> class K2(D,B,E): pass
>>> class K3(D,A):   pass
>>> class Z(K1,K2,K3): pass

以下是根據 C3 MRO 的線性化（讀者應練習驗證這些線性化並繪製繼承圖 ;-)
：

   L[A] = A O
   L[B] = B O
   L[C] = C O
   L[D] = D O
   L[E] = E O
   L[K1]= K1 A B C O
   L[K2]= K2 D B E O
   L[K3]= K3 D A O
   L[Z] = Z K1 K2 K3 D A B C E O

Python 2.2 給出了 A、B、C、D、E、K1、K2 和 K3 完全相同的線性化，但是 Z
的線性化卻不同：

   L[Z,P22] = Z K1 K3 A K2 D B C E O

顯然，這種線性化是*錯誤*的，因為 A 在 D 之前，而在 K3 的線性化中，A 在
D *之後*。換句話說，在 K3 中由 D 衍生的方法會覆寫由 A 衍生的方法，但在
Z 中（它仍然是 K3 的子類別），由 A 衍生的方法卻覆寫由 D 衍生的方法！這
是對單調性的違反。此外，Z 的 Python 2.2 線性化也與局部優先順序不一致，
因為類別 Z 的局部優先串列是 [K1, K2, K3]（K2 先於 K3），但在 Z 的線性
化中 K2 *跟隨* K3。這些問題解釋了為什麼 2.2 規則被摒棄而採用 C3 規則。


結語
====

本節適用於那些不耐煩、所有先前部分都跳過並直接滑到最後的讀者，也適用於
懶得鍛鍊大腦的程式設計師。最後，這也是針對一些自負的程式設計師，不然她
/他也不會想閱讀有關多重繼承階層中 C3 方法解析順序的文章；-)擁有這三種
美德（注意是同時擁有，不是分開）就值得獲得獎品：獎品是一個簡短的
Python 2.2 腳本，可以幫你計算 2.3 MRO 而不用傷腦筋。只需更改最後一行就
可以試跑我在本文中討論的各種範例。

   #<mro.py>

   """Samuele Pedroni 撰寫的 C3 演算法（由我改善了可讀性）。"""

   class __metaclass__(type):
       "所有類別都會被神奇地修改以便美觀地列印"
       __repr__ = lambda cls: cls.__name__

   class ex_2:
       "嚴重的順序分歧" # 來自 Guido
       class O: pass
       class X(O): pass
       class Y(O): pass
       class A(X,Y): pass
       class B(Y,X): pass
       try:
           class Z(A,B): pass # 在 Python 2.2 中會建立 Z(A,B)
       except TypeError:
           pass # 在 Python 2.3 中無法建立 Z(A,B)

   class ex_5:
       "我的第一個範例"
       class O: pass
       class F(O): pass
       class E(O): pass
       class D(O): pass
       class C(D,F): pass
       class B(D,E): pass
       class A(B,C): pass

   class ex_6:
       "我的第二個範例"
       class O: pass
       class F(O): pass
       class E(O): pass
       class D(O): pass
       class C(D,F): pass
       class B(E,D): pass
       class A(B,C): pass

   class ex_9:
       "Python 2.2 MRO 與 C3 的差異" # 來自 Samuele
       class O: pass
       class A(O): pass
       class B(O): pass
       class C(O): pass
       class D(O): pass
       class E(O): pass
       class K1(A,B,C): pass
       class K2(D,B,E): pass
       class K3(D,A): pass
       class Z(K1,K2,K3): pass

   def merge(seqs):
       print '\n\nCPL[%s]=%s' % (seqs[0][0],seqs),
       res = []; i=0
       while 1:
         nonemptyseqs=[seq for seq in seqs if seq]
         if not nonemptyseqs: return res
         i+=1; print '\n',i,'round: candidates...',
         for seq in nonemptyseqs: # 在序列頭部中尋找合併候選者
             cand = seq[0]; print ' ',cand,
             nothead=[s for s in nonemptyseqs if cand in s[1:]]
             if nothead: cand=None # 拒絕候選者
             else: break
         if not cand: raise "不一致的階層結構"
         res.append(cand)
         for seq in nonemptyseqs: # 移除候選者
             if seq[0] == cand: del seq[0]

   def mro(C):
       "根據 C3 計算類別優先串列 (mro)"
       return merge([[C]]+map(mro,C.__bases__)+[list(C.__bases__)])

   def print_mro(C):
       print '\nMRO[%s]=%s' % (C,mro(C))
       print '\nP22 MRO[%s]=%s' % (C,C.mro())

   print_mro(ex_9.Z)

   #</mro.py>

就這樣，各位，

   祝使用愉快！


資源
====

[1] Samuele Pedroni 發起的 python-dev 主題討論：
    https://mail.python.org/pipermail/python-
    dev/2002-October/029035.html

[2] 論文 *A Monotonic Superclass Linearization for Dylan*:
    https://doi.org/10.1145/236337.236343

[3] Guido van Rossum 的文章 *Unifying types and classes in Python 2.2*
    ：https://web.archive.org/web/20140210194412/http://www.python.or
    g/download/releases/2.2.2/descrintro
