2009-12-04 19 views
71

在書中的Python果殼中的(第2版)存在使用
老樣式類證明方法是如何在經典的分辨率才能解決,
它是如何與新的順序不同的例子。新風格類中的方法解析順序(MRO)?

我試着用新樣式重寫示例來嘗試相同的示例,但結果與使用舊樣式類獲得的結果沒有什麼不同。我用來運行示例的python版本是2.5.2。下面是例子:

class Base1(object): 
    def amethod(self): print "Base1" 

class Base2(Base1): 
    pass 

class Base3(object): 
    def amethod(self): print "Base3" 

class Derived(Base2,Base3): 
    pass 

instance = Derived() 
instance.amethod() 
print Derived.__mro__ 

呼叫instance.amethod()打印Base1,但按我的MRO的理解與類新風格的輸出應該是Base3。呼叫Derived.__mro__打印:

(<class '__main__.Derived'>, <class '__main__.Base2'>, <class '__main__.Base1'>, <class '__main__.Base3'>, <type 'object'>)

我不知道如果我的新樣式類MRO的理解是不正確或者說我做一個愚蠢的錯誤,我不能夠察覺。請幫助我更好地瞭解MRO。

回答

144

傳統與新風格類的分辨率順序之間的關鍵差異來自相同的祖先類在「天真」,深度優先的方法中出現多次 - 例如,考慮「鑽石繼承」的情況:

>>> class A: x = 'a' 
... 
>>> class B(A): pass 
... 
>>> class C(A): x = 'c' 
... 
>>> class D(B, C): pass 
... 
>>> D.x 
'a' 

這裏,傳統型的,分辨率順序爲d - B - 一個 - C - 答:所以找了DX時,A是分辨率順序的第一個基地,以解決它,從而隱藏在C定義。雖然:

>>> class A(object): x = 'a' 
... 
>>> class B(A): pass 
... 
>>> class C(A): x = 'c' 
... 
>>> class D(B, C): pass 
... 
>>> D.x 
'c' 
>>> 

這裏,新的風格,順序是:

>>> D.__mro__ 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, 
    <class '__main__.A'>, <type 'object'>) 

A被迫來到分辨率順序只有一次,畢竟它的子類,從而使覆蓋(即C的重寫會員x)實際上明智地工作。

這就是應該避免使用舊式類的原因之一:具有「類鑽石」模式的多重繼承對他們不起作用,而與新式相關。

+1

「[祖先類] A [is]被強制僅在其所有子類中以分辨順序進入,因此覆蓋(即C的成員x的覆蓋)實際上是明智的。」 - *主顯節!*由於這句話,我可以再次在我的腦海裏做MRO。 \ o /非常感謝。 – Esteis 2015-02-13 09:56:14

5

你得到的結果是正確的。試着改變基類的Base3Base1與同一層級比較經典類:

class Base1(object): 
    def amethod(self): print "Base1" 

class Base2(Base1): 
    pass 

class Base3(Base1): 
    def amethod(self): print "Base3" 

class Derived(Base2,Base3): 
    pass 

instance = Derived() 
instance.amethod() 


class Base1: 
    def amethod(self): print "Base1" 

class Base2(Base1): 
    pass 

class Base3(Base1): 
    def amethod(self): print "Base3" 

class Derived(Base2,Base3): 
    pass 

instance = Derived() 
instance.amethod() 

現在輸出:

Base3 
Base1 

閱讀this explanation以獲取更多信息。

1

您會看到這種行爲,因爲方法分辨率是深度優先,而不是寬度優先。 Dervied的繼承看起來像

  Base2 -> Base1 
     /
Derived - Base3 

所以instance.amethod()

  1. 檢查和Base2,沒有發現amethod方法。
  2. 看到Base2已從Base1繼承,並檢查Base1。 Base1有一個amethod,所以它被調用。

這反映在Derived.__mro__。只需遍歷Derived.__mro__,並在找到要查找的方法時停止。

+0

我懷疑我得到「Base1」作爲答案的原因是因爲方法分辨率是深度優先,我認爲它比深度優先方法更多。參見丹尼斯的例子,如果它是深度優先的o/p應該是「Base1」。另請參閱您提供的鏈接中的第一個示例,其中顯示的MRO指示方法分辨率不僅僅由深度優先遍歷確定。 – sateesh 2009-12-04 18:55:22

+0

對不起,丹尼斯提供了MRO文檔的鏈接。請檢查一下,我誤以爲你提供了python.org的鏈接。 – sateesh 2009-12-04 18:58:11

+4

這通常是深度優先的,但有一些聰明的人可以處理鑽石般的繼承,就像Alex解釋的那樣。 – jamessan 2009-12-04 19:18:29

14

Python的方法解析順序實際上比理解菱形圖案更復雜。去真的瞭解它,看看C3 linearization。我發現在擴展跟蹤訂單的方法時使用print語句確實有幫助。例如,你認爲這種模式的輸出是什麼? (注: 'X' 的假設是兩條相交的邊緣,而不是一個節點^意味着調用super()的方法)

class G(): 
    def m(self): 
     print("G") 

class F(G): 
    def m(self): 
     print("F") 
     super().m() 

class E(G): 
    def m(self): 
     print("E") 
     super().m() 

class D(G): 
    def m(self): 
     print("D") 
     super().m() 

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

class B(D, E, F): 
    def m(self): 
     print("B") 
     super().m() 

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


#  A^ 
# /\ 
# B^ C^ 
# /| X 
# D^ E^ F^ 
# \ |/
# G 

你拿到A B d C^E F G?

x = A() 
x.m() 

很多的嘗試錯誤後,我來到了C3線性的非正式圖論的解釋如下:(有人請讓我知道這是不對的。)

考慮下面的例子:

class I(G): 
    def m(self): 
     print("I") 
     super().m() 

class H(): 
    def m(self): 
     print("H") 

class G(H): 
    def m(self): 
     print("G") 
     super().m() 

class F(H): 
    def m(self): 
     print("F") 
     super().m() 

class E(H): 
    def m(self): 
     print("E") 
     super().m() 

class D(F): 
    def m(self): 
     print("D") 
     super().m() 

class C(E, F, G): 
    def m(self): 
     print("C") 
     super().m() 

class B(): 
    def m(self): 
     print("B") 
     super().m() 

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

# Algorithm: 

# 1. Build an inheritance graph such that the children point at the parents (you'll have to imagine the arrows are there) and 
# keeping the correct left to right order. (I've marked methods that call super with ^) 

#   A^ 
#  /| \ 
# / | \ 
# B^  C^ D^ I^ 
#  /| \//
#  /| X / 
# / |/ \/ 
# E^ F^ G^ 
#  \ | /
#  \ |/
#   H 
# (In this example, A is a child of B, so imagine an edge going FROM A TO B) 

# 2. Remove all classes that aren't eventually inherited by A 

#   A^ 
#  /| \ 
# / | \ 
# B^  C^ D^ 
#  /| \/ 
#  /| X  
# / |/ \ 
# E^ F^ G^ 
#  \ | /
#  \ |/
#   H 

# 3. For each level of the graph from bottom to top 
#  For each node in the level from right to left 
#   Remove all of the edges coming into the node except for the right-most one 
#   Remove all of the edges going out of the node except for the left-most one 

# Level {H} 
# 
#   A^ 
#  /| \ 
# / | \ 
# B^  C^ D^ 
#  /| \/ 
#  /| X  
# / |/ \ 
# E^ F^ G^ 
#    | 
#    | 
#    H 

# Level {G F E} 
# 
#   A^ 
#  /| \ 
# / | \ 
# B^ C^ D^ 
#   | \/ 
#   | X  
#   | | \ 
#   E^F^ G^ 
#    | 
#    | 
#    H 

# Level {D C B} 
# 
#  A^ 
#  /| \ 
# /| \ 
# B^ C^ D^ 
#  | | 
#  | |  
#  | | 
#  E^ F^ G^ 
#   | 
#   | 
#   H 

# Level {A} 
# 
# A^ 
# | 
# | 
# B^ C^ D^ 
#  | | 
#  | | 
#  | | 
#  E^ F^ G^ 
#    | 
#    | 
#    H 

# The resolution order can now be determined by reading from top to bottom, left to right. A B C E D F G H 

x = A() 
x.m() 
+0

你應該改正你的第二個代碼:你已經把類「I」作爲第一行,並使用超級,所以它找到超類「G」,但「我」是第一類,所以它永遠不會找到「G」類,因爲沒有「G」上的「I」。將類「I」放在「G」和「F」之間:) – 2016-11-07 15:43:03

+0

示例代碼不正確。 'super'需要參數。 – danny 2017-11-21 12:07:56

+0

在類定義super()中不需要參數。請參見[https://docs.python.org/3/library/functions.html#super](https://docs.python.org/3/library/functions.html#super) – Ben 2017-11-21 15:05:25