文章目錄
-
-
-
- 1. 鑽石問題
- 2. MRO
- 3. Super
-
- 3.1 前世
- 3.2 今生
- 參考文獻
-
-
人們普遍認為,多重繼承是危險的或不好的,這主要是由于程式設計語言的多重繼承機制執行不當,尤其是由于使用不當而引起的,Java甚至不支援多重繼承,而C++支援。Python具有複雜且精心設計的多重繼承方法。
在類定義中,當一個名為SubClassName的子類從BaseClass1,BaseClass2,BaseClass3這樣的父類繼承時,具體格式如下所示:
class SubclassName(BaseClass1, BaseClass2, BaseClass3, ...):
pass
顯然,父類BaseClass1,BaseClass2,BaseClass3能從其他父類繼承,于是我們可以得到一課繼承樹:
1. 鑽石問題
“鑽石問題”(有時被稱為“緻命的死亡鑽石”)是一個常用術語,當兩個類B和C繼承自父類A,而另一個類D繼承自B和C時,會産生歧義。如果A中有一個方法“m”被B或C(甚至兩者)重寫,那麼D繼承該方法的哪個版本?可能是A、B或C。
第一個鑽石問題的配置是這樣的:B和C都覆寫A的方法m。代碼如下:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
class C(A):
def m(self):
print("m of C called")
class D(B,C):
pass
d=D()
d.m()
在本例中,d為D類的執行個體,調用d.m()方法,輸出如下:
m of B called
如果D類頭部變為" class D(C,B) “,輸出為” m of C called "。
2. MRO
如果方法m被B或C其中一個類重寫,例如在C類中被重寫:
class A:
def m(self):
print("m of A called")
class B(A):
pass
class C(A):
def m(self):
print("m of C called")
class D(B,C):
pass
d=D()
d.m()
在python2和python3的輸出結果是不同的:
#Python2輸出結果
m of A called
#Python3輸出結果
m of C called
為了在Python2中擁有與Python3相同的繼承行為,每個類都必須從object類繼承。上例中的類A不是從object繼承的,是以如果我們用python2調用腳本,就會得到一個所謂的舊樣式類。與舊樣式類相對,新樣式類的第一個父類繼承自Python根級别的object類,從格式上看,新舊樣式類差別如下:
# Old style class
class OldStyleClass:
pass
# New style class
class NewStyleClass(object):
pass
兩種聲明樣式中的方法解析順序(MRO)不同。舊樣式類使用DLR算法,而新樣式類使用C3線性化算法(C3 Linearization algorithm)來實作多種繼承的方法解析。
舊樣式類的多重繼承由兩個規則控制:深度優先,然後從左到右。在上例中,DLR算法的解析順序将為D,B,A,C,A,但是,A不能兩次出現,是以順序将為D,B,A,C。如果将A的定義頭部更改為" class A(object): ",那麼在這兩個Python版本中,将得到相同的結果。
C3線性化算法用于消除DLR算法産生的不一緻性,具體計算過程我沒看,也不懂,是以就不寫了。個人總結MRO的排序遵循以下原則:
- 子類優先于父類,子類全部排序完才會繼續檢查父類
- 如果一個子類繼承自多個父類,則父類将按照子類定義中出現的順序進行排序
通過使用__mro__屬性或mro方法,可以顯示類的MRO順序。示例如下:
class A():
def m(self):
print("m of A called")
class B(A):
pass
class C(A):
def m(self):
print("m of C called")
class D(B,C):
pass
print(D.__mro__)
在python3的環境下執行得到MRO的順序為:
3. Super
3.1 前世
将前面的示例稍加改動,儲存為test.py檔案:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
A.m(self)
class C(A):
def m(self):
print("m of C called")
A.m(self)
class D(B,C):
def m(self):
print("m of D called")
B.m(self)
C.m(self)
在指令行建立對象:
>>> from test import D
>>> x=D()
>>> x.m()
m of D called
m of B called
m of A called
m of C called
m of A called
這裡A類的方法m被反複調用了兩次,如果修改為隻調用一次,有兩種思路:一種是将B和C類的m方法分情況編寫;一種是使用super()函數。第一種方法不符合python的風格,代碼如下:
class A:
def m(self):
print("m of A called")
class B(A):
def _m(self):
print("m of B called")
def m(self):
self._m()
A.m(self)
class C(A):
def _m(self):
print("m of C called")
def m(self):
self._m()
A.m(self)
class D(B,C):
def m(self):
print("m of D called")
B._m(self)
C._m(self)
A.m(self)
3.2 今生
第二種方法使用super(),很pythonic:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
super().m()
class C(A):
def m(self):
print("m of C called")
super().m()
class D(B,C):
def m(self):
print("m of D called")
super().m()
兩種方法輸出一緻:
>>> from test import D
>>> x=D()
>>> x.m()
m of D called
m of B called
m of C called
m of A called
上面描述的是super函數的第一個用途,調用父類方法。super函數的第二個用途是:確定父類被正确初始化。當執行個體使用__init__方法初始化時,經常會使用super函數,示例如下:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
print('A1.__init__')
super().__init__()
print('A2.__init__')
class B(Base):
def __init__(self):
print('B1.__init__')
super().__init__()
print('B2.__init__')
class C(A,B):
def __init__(self):
print('C1.__init__')
super().__init__() # Only one call to super() here
print('C2.__init__')
将代碼儲存為test.py,建立對象執行個體,執行結果為:
>>> from test import *
>>> c=C()
C1.__init__
A1.__init__
B1.__init__
Base.__init__
B2.__init__
A2.__init__
C2.__init__
>>> b=B()
B1.__init__
Base.__init__
B2.__init__
>>> a=A()
A1.__init__
Base.__init__
A2.__init__
從結果可以看出,super()的本質是函數,其行為也符合函數嵌套的執行順序。
查詢各類的mro順序如下:
>>> C.mro()
[<class 'test.C'>, <class 'test.A'>, <class 'test.B'>, <class 'test.Base'>, <class 'object'>]
>>> B.mro()
[<class 'test.B'>, <class 'test.Base'>, <class 'object'>]
>>> A.mro()
[<class 'test.A'>, <class 'test.Base'>, <class 'object'>]
>>> Base.mro()
[<class 'test.Base'>, <class 'object'>]
參考文獻
https://www.programiz.com/python-programming/methods/built-in/super
https://www.programiz.com/python-programming/multiple-inheritance
https://www.geeksforgeeks.org/method-resolution-order-in-python-inheritance/?ref=rp
https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html
https://www.python-course.eu/python3_multiple_inheritance.php