天天看點

了解Python中的多重繼承

文章目錄

        • 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能從其他父類繼承,于是我們可以得到一課繼承樹:

了解Python中的多重繼承

1. 鑽石問題

    “鑽石問題”(有時被稱為“緻命的死亡鑽石”)是一個常用術語,當兩個類B和C繼承自父類A,而另一個類D繼承自B和C時,會産生歧義。如果A中有一個方法“m”被B或C(甚至兩者)重寫,那麼D繼承該方法的哪個版本?可能是A、B或C。

了解Python中的多重繼承

    第一個鑽石問題的配置是這樣的: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