標籤:

Python多重繼承是否始終是遵循從左到右 深度優先的規則?

PS:本人Python萌新一枚 在自學多重繼承的時候 寫了一個demo測試繼承順序和函數覆蓋的優先順序選擇時候出現了一點不可思議的事情 o_O,糾結了好久...

#python 3.5.2

class A:
def __init__(self, a):
print("A")
self.a = a

def comeon(self):
print("A.comeon")

class B(A):
def __init__(self, b):
super().__init__(b)
print("B")
self.b = b

def comeon(self):
print("B.comeon")

class C(A):
def __init__(self, c):
super().__init__(c)
print("C")
self.c = c

def comeon(self):
print("C.comeon")

class D(B, C):
def __init__(self, d):
super().__init__(d)
print("D")
self.d = d

d = D("d")
d.comeon()

最後的執行到d=D("d")的時候運行結果卻是 A C B D

但是 文檔上寫的很明確啊:

# 9.5.1. Multiple Inheritance

For most purposes, in the simplest cases, you can think of the search for
attributes inherited from a parent class as depth-first, left-to-right, not
searching twice in the same class where there is an overlap in the hierarchy
.
Thus, if an attribute is not found in DerivedClassName, it is searched for in Base1,
then (recursively) in the base classes of Base1,
and if it was not found there, it was searched for in Base2,
and so on.

也就是說 從左到右 深度優先遍歷,而此時的繼承鏈 如圖所示:

A

/

B C

/

D

那麼結果應該是 A B C D 為何輸出結果 是A C B D ?求大神解答


首先說明一個問題,Python的多重繼承確實正如文檔所言是深度優先從左至右不重複,唯一的問題是,關於『優先』其實是指最貼近繼承樹葉部的,左側的優先,會最後繼承,從而覆蓋其它繼承得來的效果。

在Python里,當你新構造一個對象時,有兩個步驟:首先是自底向上,從左至右調用__new__,然後再依照遞歸棧依次調用__init__。這個問題可以用以下代碼說明

class A:
def __new__(cls, *argv, **kwargs):
print("nA")
return super().__new__(cls)

def __init__(self, a):
print("A")
self.a = a

def comeon(self):
print("A.comeon")

class B(A):
def __new__(cls, *argv, **kwargs):
print("nB")
return super().__new__(cls)

def __init__(self, b):
super().__init__(b)
print("B")
self.b = b

def comeon(self):
print("B.comeon")

class C(A):
def __new__(cls, *argv, **kwargs):
print("nC")
return super().__new__(cls)

def __init__(self, c):
super().__init__(c)
print("C")
self.c = c

def comeon(self):
print("C.comeon")

class D(B, C):
def __new__(cls, *argv, **kwargs):
print("nD")
return super().__new__(cls)

def __init__(self, d):
super().__init__(d)
print("D")
self.d = d

d = D("d")
d.comeon()

首先看到:d.comeon是從左自右得來的左邊的那個B的comeon。那麼如何實現這樣的效果呢?很簡單,讓B的init最後一個執行,就能覆蓋掉C和D寫入的comeon。

所以實際調用new的順序就是D--B--C--A,之後遞歸棧回過頭來初始化,調用init的順序就是A--C--B--D,只有這樣才能保證B里的comeon能夠覆蓋掉D的init帶入的comeon和C帶入的comeon,同樣保證如果你的D里有個comeon,它是最後一個init的,將最後寫入而覆蓋掉其它的。


MRO( Method resolution order) 問題, 不會也沒關係, 用help()就好了, 英文好的話去Youtube看這個演講:

Raymond Hettinger - Super considered super! - PyCon 2015

https://www.youtube.com/watch?v=EiOglTERPEot=804s

in:

help(d)

out:

Help on D in module __main__ object:

class D(B, C)
| Method resolution order:
| D
| B
| C
| A
| builtins.object
|
| Methods defined here:
|
| __init__(self, d)
| Initialize self. See help(type(self)) for accurate signature.
|
| ----------------------------------------------------------------------
| Methods inherited from B:
|
| comeon(self)
|
| ----------------------------------------------------------------------
| Data descriptors inherited from A:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)


才學Python3,但感覺並不難理解。

B、C繼承A,D先後繼承了B、C。

所以調用構造方法的時候,應該D的構造方法入棧,然後B的構造方法入棧,C的構造方法入棧,最後A的構造方法入棧。所以執行的時候明顯是,A的構造方法先出棧,其次C的構造方法出棧,然後B的構造方法出棧,最後是D的構造方法出棧。

沒毛病


我記得是python2是廣度優先繼承,3是深度優先繼承。具體看orm,新式類和舊式類。


u在Python 2.7和 3.5,繼承規則也是有所不同的。

3.5主要是採用C3演算法。

引用官方文檔:

take the head of the first list, i.e L[B1][0]; if this head is not in the tail of any of the other lists, then add it to the linearization of C and remove it from the lists in the merge, otherwise look at the head of the next list and take it, if it is a good head. Then repeat the operation until all the class are removed or it is impossible to find good heads. In this case, it is impossible to construct the merge, Python 2.3 will refuse to create the class C and will raise an exception.

其他的有空再補充


剛看完 9. Classes - Python 2.7.13 documentation 文檔的類部分,發現和題主給出的並不完全相同。在這裡很明確的提到了 old-style classes 和 new-style classes 兩種,當然 Python3之後就都是新式類了。但是文檔中明確提到:

For old-style classes, the only rule is depth-first, left-to-right.

而 Python3 已經都是新式類了,自然是適用新式類的規範,原文如下。希望能幫到你。

==== Python文檔引用 ====

Python supports a limited form of multiple inheritance as well. A class definition with multiple base classes looks like this:

For old-style classes, the only rule is depth-first, left-to-right. Thus, if an attribute is not found in DerivedClassName, it is searched in Base1, then (recursively) in the base classes of Base1, and only if it is not found there, it is searched in Base2, and so on.

(To some people breadth first — searching Base2 and Base3 before the base classes of Base1 — looks more natural. However, this would require you to know whether a particular attribute of Base1 is actually defined in Base1 or in one of its base classes before you can figure out the consequences of a name conflict with an attribute of Base2. The depth-first rule makes no differences between direct and inherited attributes of Base1.)

For new-style classes, the method resolution order changes dynamically to support cooperative calls to super(). This approach is known in some other multiple-inheritance languages as call-next-method and is more powerful than the super call found in single-inheritance languages.

==== Python文檔引用 ====


看書Python高級編程 (豆瓣) 第三章. 有詳細解釋..


推薦閱讀:

記事本能知道文本的編碼(ANSI,UTF-8等),但python要open時設置encoding?
為什麼 Python、Ruby 等語言棄用了自增運算符?
如何找到適合需求的 Python 庫?
使用python語言如何保密源代碼以防止逆向工程?
Python 3.x 上 str 與 bytes 轉換函數是什麼?

TAG:Python | Python3x |