Python 新式類繼承關系的 C3 算法(Python 2.3 的方法解析順序,MRO)
作者:Michele Simionato.
翻譯:劉碩
原文連結:https://www.python.org/download/releases/2.3/mro/
摘要:本文檔面向于想要了解Python 2.3版本中 C3 方法解析順序的 Python程式開發人員。盡管它對新手而言不是很友好,本文檔裡面還是提供了很多有助于了解的解決問題的例子。目前我還不知道有哪個公開的文檔解決了類似的問題,是以本文檔還是很有用的。
免責聲明:
本人(指作者而非翻譯者,翻譯者按)将此文檔以Python 2.3 許可證贈送給Python軟體基金會(Python Software Foundation。照例,我需要提醒讀者下面的内容應該是正确的,但是我不能給出任何保證。請擦亮眼睛,風險自負!
鳴謝:
感謝Python郵件清單中所有給我提供支援的人。感謝Paul Foley指出了很多微小瑕疵,并且建議我加入局部優先排序部分。感謝David Goodger幫忙排版了reStructuredText部分。感謝David Mertz幫忙編輯。感謝Joan G. Stark提供爬蟲圖檔。最後,感謝Guido van Rossum熱情地把本文檔加入到Python2.3的官方首頁中。
.-=-. .--. __ .' '. / " ) _ .' '. / .-. \ / .-'\ ( \ / .-. \ / / \ \ / / ^ \ `-` / \ `-' / \ `-` / jgs`-.-` '.____.' `.____.'
我們開始吧
Felix qui potuit rerum cognoscere causas – Virgilius
興趣是最好的老師 – 維吉爾
一切要從 Samuele Pedroni 在 Python 開發郵件清單送出的文章說起1。在他釋出的文章中,Samuele指出Python 2.2 的方法解析順序并不是單調的。他提出可以用 C3 方法解析順序來替換 Python 2.2 中的方法。Guido 贊同他的見解,是以在 Python 2.3 中使用了C3 算法。C3 方法本身和 Python 一點關系也沒有,因為它是一個 Dylan 開發者發明的,而且它用來讓口吃患者用紙來描述2。本文為想要了解Python中這些更改的原因的 Python 愛好者們提供了一個(但願)通俗易懂的 C3 算法介紹。
首先,我要指出,我将要讨論的僅适用于在 Python 2.2 中引入的新式類:經典類保留了它們原來的方法解析順序,深度優先,先左後右。是以,原有的經典類的代碼不會收到影響;而且即便理論上來說 Python 2.2 的新式類的代碼會被破壞,但是實際上 C3 解析順序和 Python 2.2 中的方法解析順序相差無幾,是以基本也不會有什麼影響。是以:
不必擔心!
此外,除非你重度使用多繼承,并且你有使用了非凡的繼承,你是不必了解 C3 算法的,你可以直接跳過這篇文章。不過話說回來,如果你真的想要知道多繼承究竟是如何實作的,那麼這篇文章就是為你而寫。好消息是,正如你期待的那樣,這些原理并不是非常複雜。
讓我們從一些基礎的定義開始。
- 假設在一個負載的多繼承關系中,有一個 C 類。有一個很重要的任務:我們要在這些複雜的繼承關系中明确方法的執行順序。例如,分辨出 C 類的繼承順序。
- 包括它本身咋内的 C 類繼承順序清單,以繼承關系由近到遠排序,被稱作 C 類的優先級清單,或者線性化清單。
- 方法解析順序(MRO)就是構造線性化清單的一串規則。在 Python 圈中,習慣用語“C 的MRO”的意義等同于 C 類的線性化清單。
- 例如,在單繼承體系中,如果 C 類是 C1 類的子類,C1 類又是 C2 類的子類。那麼 C 類的線性化清單就理所當然是 [C, C1 , C2]。然而,在多繼承體系中,構造線性化清單的方式就要稍微複雜一點,因為構造一個同時滿足 局部優先 和 單調 的線性化清單優點不容易。
- 我過會兒會讨論局部優先順序,不過我要先在這裡給出單調的定義。MRO 是單調的可以這樣了解:在 C 類的線性化清單中,如果 C1 類排在 C2 類的前面,那麼在任何一個 C 類的子類中, C1 類都要優先于 C2 類。如果MRO不滿足單調性,無意間的衍生新類的操作就可能改變方法的解析順序。這些改變會潛移默化地引入一些難以預測的 bug。過會兒會有這種 bug 出現的例子。
- 并不是所有的類都能實作線性化。在複雜的繼承關系中會有很多不可能讓派生出的新類的所有屬性都滿足線性化的情況。
我在這裡給出一個這種情況例子。康康下面的繼承:
>>> 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 類。因為在 A 類中, X 類的優先級高于 Y 類,但是在 B 類中,Y 類的優先級高于 X 類。這樣一來,C 類中的方法解析順序就沖突了。
對于這種情況,在 Python 2.3 中會抛出一個異常(TypeError: MRO conflict among bases Y, X,即:類型錯誤:基類 Y 和 X 方法解析順序沖突)來避免小白程式員制造沖突的繼承關系。然而 Python 2.2 不會抛出異常,而是選擇一個 專門的 順序(在這種情況下會是CABXYO)。
_ .-=-. .-==-. { } __ .' O o '. / -<' ) { } .' O'. / o .-. O \ / .--v` { } / .-. o\ /O / \ o\ /O / \ `-` / \ O`-'o / \ O`-`o / jgs `-.-` '.____.' `.____.'
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].
現在我可以解釋 Python 2.3 中,MRO 是如何運作的了。
設想一個多重繼承體系的 C 類,繼承自基類 B1, B2, … , BN。我們要計算出 C 類的線性化清單 L[C]。其規則如下:
C 類的線性化清單是 C 類與其融合(merge)後的所有父類線性化清單再加上所有父類組成新清單的和。
用符号來表示就是:
L[C(B1 … BN)] = C + merge(L[B1] … L[BN], B1 … BN)
特别地,如果 C 類是沒有父類的
object
類,它的線性化清單就很簡單了:
L[object] = object.
然而,通常下還是要用下面的規則來計算融合(merge)的結果:
首先取出清單的頭部(head),例如 L[B1][0];如果這個頭部沒有在其他任何清單的尾部(tail),就把它加入到 C 類的線性化清單中,并且把它從融合(merge)中全部移除。否則就檢視并移除下一個清單的頭部,如果它是一個符合條件的頭部的話。重複操作直到所有的類都被移除或者不可能找到合适的頭部了。在這種情況下,不可能進行融合,Python 2.3 會拒絕建立 C 類,并且抛出一個異常。
這個規則確定了融合操作 保留 順序,如果順序可以被保留的話。另一方面,如果順序無法被保留(就像前面的一串順序沖突的例子),融合将無法計算。
如果 C 類隻有一個父類(單繼承)的融合是非常容易計算的。在這種情況下:
L[C(B)] = C + merge(L[B],B) = C + L[B]
然而,多繼承的情況就變得稍微複雜一點。我覺得如果不舉幾個例子的話,你們是沒辦法了解的 😉
.-'-. /' `\ /' _.-.-._ `\ | (|) (|) | | \__"__/ | \ |v.v| / \ | | | / `\ |=^-| /' `|=-=|' | - | |= | |-=-| _.-=-=|= -|=-=-._ ( |___| ) ( `-=-=-=-=-=-=-=-` ) (`-=-=-=-=-=-=-=-=-`) (`-=-=-=-=-=-=-=-=-`) (`-=-=-=-=-=-=-=-`) (`-=-=-=-=-=-=-`) jgs `-=-=-=-=-=-=-`
例子
第一個例子。思考一下下面的繼承關系:
>>> 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 '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <type '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 類。
__ (\ .-. .-. /_") \\_//^\\_//^\\_// jgs `"` `"` `"`
不良的方法解析順序
當一個方法解析順序破壞了本地有限順序和單調性,那它就是 不良 的。在這個部分,我将會把 Python 2.2 中經典類和新式類的不良 MRO 都介紹給你。
讓我們以簡單點的局部優先排序開始。思考下面的例子:
>>> F=type('Food',(),{'remember2buy':'spam'}) >>> E=type('Eggs',(F,),{'remember2buy':'eggs'}) >>> G=type('GoodFood',(F,E),{}) # 這在 Python 2.3 中是一個錯誤!
其繼承關系圖
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'
這個例子破壞了局部優先順序,因為在 Python 2.2 的局部優先清單中,比如 G 的父類,沒有被 G 的線性化清單保護(譯者按:P22 指代 Python 2.2,這裡特指 Python 2.2 的新式類):
L[G,P22]= G E F object # F *跟着* E
有些人可能辯解說,在 Python 2.2 的線性化清單中,F 被排在 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 的尾部。
這個問題正确的解決辦法是設計一個不沖突的繼承關系,比如,讓 G 繼承 E 和 F(先繼承更具體的)而不是繼承自 F 和 E。這種情況的 MRO 無疑就是 GEF 了。
O | F (spam) / | (eggs) E | \ | G (eggs, no doubt)
Python 2.3 強制要求程式員寫合理的繼承關系(或者,至少,不太可能犯錯的繼承關系)。
與此相關,我要指出的是,ython 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(經典類和新式類都是如此)遇到這種情況,不會抛出任何異常。
最後,我要指出我們能從這個例子中學到的兩個技巧:
- 不要理會名字,MRO 也會解析屬性的順序,而非僅僅解析方法順序;
- Python 愛好者預設的食物是午餐肉!(雖然你已經知道了 ;-)
__ (\ .-. .-. /_") \\_//^\\_//^\\_// jgs `"` `"` `"`
我們已經讨論完局部優先順序,現在讓我們來研究單調性的問題吧。我的目标是告訴你們 經典類和 Python 2.2 中的新式類都不是單調的。
要證明經典類的 MRO 不單調就很容易了,隻要看看鑽石繼承關系圖就足夠:
C / \ / \ A B \ / \ / D
大家很容易就能找到沖突(譯者按:P21 指代 Python 2.1,即經典類):
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 類的方法,但是在 K3 的子類 Z 中,繼承自 A 類的方法要優先于繼承自 D 類的方法!這違背了單調性原則。此外,Python 2.2 中 Z 的線性化清單也和局部優先順序沖突,因為 Z 類的局部優先順序清單是 [K1, K2, K3](K2 優先于 K3),然而在 Z 的線性化清單中,K2 落後于 K3。這些麻煩解釋了為什麼 2.2 的規則會被 C3 規則取代。
__ (\ .-. .-. .-. .-. .-. .-. .-. .-. /_") \\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_// jgs `"` `"` `"` `"` `"` `"` `"` `"` `"`
寫在最後
這部分寫給那些沒有耐心的,略過了前面所有内容,直接跳到最後的讀者。這個部分也寫給不想借此訓練他/她頭腦的懶懶的程式員。最後,謹以此獻給有一點點高傲的程式員,不這樣的話,他/她是不會讀一篇關于多繼承關系 C3 方法解析順序的文章的 😉 集齊這三種美德(單獨 有一種不算的喲)就可以召喚神龍啦:送給你一個簡短的不需要犧牲你的腦細胞就可以計算 Python 2.3 MRO 清單的 Python 2.2 腳本。隻需要稍微改一下最後一行代碼就可以跟我在這篇文章中讨論的所有例子愉快地玩耍啦。
#<mro.py> """Samuele Pedroni 開發的C3算法(Michele Simionato 讓它變得通俗易懂,我把它翻譯成中文)""" class __metaclass__(type): "所有的類都在元類層面被修飾成很好列印的形式" __repr__ = lambda cls: cls.__name__ class ex_2: "嚴重的順序沖突" #From 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 # Z(A,B) 在 Python 2.3 中不會被建立 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: 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 #reject candidate 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>
上面的代碼需要用 Python 2 才能運作,譯者把作者原來的代碼運作了一遍,結果如下。讀者也可以修改作者的代碼,獲得不同例子的結果。
CPL[<type 'object'>]=[[<type 'object'>], []] 1 round: candidates... <type 'object'> CPL[O]=[[O], [<type 'object'>], [<type 'object'>]] 1 round: candidates... O 2 round: candidates... <type 'object'> CPL[A]=[[A], [O, <type 'object'>], [O]] 1 round: candidates... A 2 round: candidates... O 3 round: candidates... <type 'object'> CPL[<type 'object'>]=[[<type 'object'>], []] 1 round: candidates... <type 'object'> CPL[O]=[[O], [<type 'object'>], [<type 'object'>]] 1 round: candidates... O 2 round: candidates... <type 'object'> CPL[B]=[[B], [O, <type 'object'>], [O]] 1 round: candidates... B 2 round: candidates... O 3 round: candidates... <type 'object'> CPL[<type 'object'>]=[[<type 'object'>], []] 1 round: candidates... <type 'object'> CPL[O]=[[O], [<type 'object'>], [<type 'object'>]] 1 round: candidates... O 2 round: candidates... <type 'object'> CPL[C]=[[C], [O, <type 'object'>], [O]] 1 round: candidates... C 2 round: candidates... O 3 round: candidates... <type 'object'> CPL[K1]=[[K1], [A, O, <type 'object'>], [B, O, <type 'object'>], [C, O, <type 'object'>], [A, B, C]] 1 round: candidates... K1 2 round: candidates... A 3 round: candidates... O B 4 round: candidates... O O C 5 round: candidates... O 6 round: candidates... <type 'object'> CPL[<type 'object'>]=[[<type 'object'>], []] 1 round: candidates... <type 'object'> CPL[O]=[[O], [<type 'object'>], [<type 'object'>]] 1 round: candidates... O 2 round: candidates... <type 'object'> CPL[D]=[[D], [O, <type 'object'>], [O]] 1 round: candidates... D 2 round: candidates... O 3 round: candidates... <type 'object'> CPL[<type 'object'>]=[[<type 'object'>], []] 1 round: candidates... <type 'object'> CPL[O]=[[O], [<type 'object'>], [<type 'object'>]] 1 round: candidates... O 2 round: candidates... <type 'object'> CPL[B]=[[B], [O, <type 'object'>], [O]] 1 round: candidates... B 2 round: candidates... O 3 round: candidates... <type 'object'> CPL[<type 'object'>]=[[<type 'object'>], []] 1 round: candidates... <type 'object'> CPL[O]=[[O], [<type 'object'>], [<type 'object'>]] 1 round: candidates... O 2 round: candidates... <type 'object'> CPL[E]=[[E], [O, <type 'object'>], [O]] 1 round: candidates... E 2 round: candidates... O 3 round: candidates... <type 'object'> CPL[K2]=[[K2], [D, O, <type 'object'>], [B, O, <type 'object'>], [E, O, <type 'object'>], [D, B, E]] 1 round: candidates... K2 2 round: candidates... D 3 round: candidates... O B 4 round: candidates... O O E 5 round: candidates... O 6 round: candidates... <type 'object'> CPL[<type 'object'>]=[[<type 'object'>], []] 1 round: candidates... <type 'object'> CPL[O]=[[O], [<type 'object'>], [<type 'object'>]] 1 round: candidates... O 2 round: candidates... <type 'object'> CPL[D]=[[D], [O, <type 'object'>], [O]] 1 round: candidates... D 2 round: candidates... O 3 round: candidates... <type 'object'> CPL[<type 'object'>]=[[<type 'object'>], []] 1 round: candidates... <type 'object'> CPL[O]=[[O], [<type 'object'>], [<type 'object'>]] 1 round: candidates... O 2 round: candidates... <type 'object'> CPL[A]=[[A], [O, <type 'object'>], [O]] 1 round: candidates... A 2 round: candidates... O 3 round: candidates... <type 'object'> CPL[K3]=[[K3], [D, O, <type 'object'>], [A, O, <type 'object'>], [D, A]] 1 round: candidates... K3 2 round: candidates... D 3 round: candidates... O A 4 round: candidates... O 5 round: candidates... <type 'object'> CPL[Z]=[[Z], [K1, A, B, C, O, <type 'object'>], [K2, D, B, E, O, <type 'object'>], [K3, D, A, O, <type 'object'>], [K1, K2, K3]] 1 round: candidates... Z 2 round: candidates... K1 3 round: candidates... A K2 4 round: candidates... A D K3 5 round: candidates... A D 6 round: candidates... A 7 round: candidates... B 8 round: candidates... C 9 round: candidates... O E 10 round: candidates... O 11 round: candidates... <type 'object'> MRO[Z]=[Z, K1, K2, K3, D, A, B, C, E, O, <type 'object'>] P22 MRO[Z]=[Z, K1, K2, K3, D, A, B, C, E, O, <type 'object'>]
就是這些了朋友們,
好好玩耍吧!
__ ("_\ .-. .-. .-. .-. .-. .-. .-. .-. /) \\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_//^\\_// jgs `"` `"` `"` `"` `"` `"` `"` `"` `"`
引用資料
- The thread on python-dev started by Samuele Pedroni: http://mail.python.org/pipermail/python-dev/2002-October/029035.html ↩︎
- The paper A Monotonic Superclass Linearization for Dylan: https://doi.org/10.1145/236337.236343 ↩︎
- Guido van Rossum’s essay, Unifying types and classes in Python 2.2: http://www.python.org/2.2.2/descrintro.html ↩︎