Python多重繼承的異構構造器

2021-07-05 08:59:06 字數 3842 閱讀 8017

在python裡面,如果你使用上qt,sqlalchemy,twisted之類各種大型類庫時候,有時候多重繼承multiple inheritance是個簡單的解決方法,但是多重繼承的複雜性總容易造成誤解和疑惑。

一般「常識」說,使用super訪問父類的屬性/方法,這種說法在多重繼承裡面是不成立的,多重繼承的類並沒有父類的概念(there is no superclass in a mi world)。類似的部落格在過去幾年被人寫了無數遍了,因為過去版本裡面python官方文件對super的解釋非常有限而且有誤導解釋,直到2.6以後的文件,才詳細說明了super在單繼承和多繼承的兩種不同工作方式。當時苦逼的程式設計師甚至不得不去翻看python原始碼才搞清楚是什麼回事。以致幾年來很多人對python的多重繼承保持懷疑態度。

python多重繼承使用method resolution order的動態演算法來解決乙個方法名的呼叫順序,mro其實說來簡單,就是乙個深度優先的繼承列表,很易理解,但隨之來的是遇到互不相同的構造器__init__引數的問題。

codepad執行結果:

123

4567

891011

1213

1415

1617

classa(

object

):def__init__

(self

, arg1):

print"init func in a, with arg1 '%s'" % arg1

super

(a,self

).__init__()

classb(

object

):def__init__

(self

, arg1, arg2):

print"init func in b, with arg1'%s', arg2 '%s'" % (arg1, arg2)

super

(b,self

).__init__

(arg1)

classc(b, a):

def__init__

(self

, arg1, arg2):

print"init func in c, with arg1'%s', arg2 '%s'" % (arg1, arg2)

super

(c,self

).__init__

(arg1, arg2)

printc.__mro__

c = c(

"c's arg1"

,"c's arg2"

)

執行結果:

init func in c, with arg1'c's arg1', arg2 'c's arg2'

init func in b, with arg1'c's arg1', arg2 'c's arg2'

init func in a, with arg1 'c's arg1'

(, , , )

可見幾個類的構造器的執行順序正是mro列表的順序。重點是多重繼承的各個類的構造器__init__之所以能夠執行,是因為每個構造器裡面都有一句super(),這個super完成mro列表中下乙個類的構造器的呼叫。

這個事實聽起來似乎很自然,但看**,b的構造器還得必須知道a的構造器的引數?b需要知道自己將會被c同時繼承a,並且呼叫a的構造?!!很荒謬,但不幸的這是mro的特點。**是可以這麼寫,但不應該,為另乙個不知道什麼時候會被一起繼承的類特地地寫**,跟面對物件設計的解耦原則相違背。

在mro方式的基礎上,這個問題是不可能有效解決的,只能避免。概括起來大概有這麼 兩種方式。

1.使用傳統類的方式,顯式呼叫被繼承類的構造器,避開super的mro自動化工作。

codepad 看執行效果: 

123

4567

891011

1213

1415

1617

classa(

object

):def__init__

(self

, arg1):

print"init func in a, with arg1 '%s'" % arg1

classb(

object

):def__init__

(self

, arg1, arg2):

print"init func in b, with arg1'%s', arg2 '%s'" % (arg1, arg2)

classc(a, b):

def__init__

(self

, arg1, arg2):

print"init func in c, with arg1'%s', arg2 '%s'" % (arg1, arg2)

#super(c, self).__init__(arg1) #這兩行

a.__init__

(self

, arg1)

#等價 b.__init__

(self

, arg1, arg2)

printc.__mro__

c = c(

"c's arg1"

,"c's arg2"

)

注意 c繼承a,b的順序已經改變。

要排除乙個容易產生的誤解。python文件裡面的super有個很顯著的note:super() only works for new-style classes. super只適用於新類。但新類並不必須使用super。

直接呼叫被繼承類__init__作為unbound方法呼叫,需要指定乙個例項,如self作為引數,依次呼叫各個被繼承類。缺點是若果這幾個被繼承類也在構造方法裡面使用這樣呼叫了同乙個上級被繼承類,會出現「爺爺類」的構造方法被呼叫多次的情況。

如果一定使用super,要注意繼承列表的順序。super(type, self).method呼叫的是mro列表中第乙個,也即繼承列表第乙個類的方法。

pyqt裡面的類內部一般(未細究)都使用__init__的方式來初始化**,因而很多pyqt的例子**都是使用qtgui.qmainwindow.__init__(self)這樣的**來初始化。當然在單繼承時候和super的寫法是等價的,但最好使用統一的原則:

乙個簡單好記的原則:

2.使用composition / association pattern的設計模式(即'is-a'轉換成'has-a')來實現相同功能,避免多重繼承。

這個方法聽起來未免有點讓人不快(破壞了原有設計思維),但實際上很可能這是更好的方式,更清晰的**,尤其是要繼承的類裡面混合了使用super__init__兩種初始化方式的時候。

原文:

Python多重繼承的異構構造器

在python裡面,如果你使用上qt,sqlalchemy,twisted之類各種大型類庫時候,有時候多重繼承multiple inheritance是個簡單的解決方法,但是多重繼承的複雜性總容易造成誤解和疑惑。一般 常識 說,使用super訪問父類的屬性 方法,這種說法在多重繼承裡面是不成立的,多...

python的多重繼承

在設計類的繼承關係時,通常,主線都是單一繼承下來的,如果需要 混入 額外的功能,通過多重繼承就可以實現,這種設計通常稱之為mixin。class animal object pass class mammal animal pass class runnable object def run sel...

python的多重繼承

python和c 一樣,支援多繼承。概念雖然容易,但是困難的工作是如果子類呼叫乙個自身沒有定義的屬性,它是按照何種順序去到父類尋找呢,尤其是眾多父類中有多個都包含該同名屬性。對經典類和新式類來說,屬性的查詢順序是不同的。現在我們分別看一下經典類和新式類兩種不同的表現 經典類 usr bin pyth...