Python從菱形繼承到多繼承

一、菱形繼承與多繼承

Python在繼承方面與Java不同,Python可以進行多繼承,而Java則不能多繼承,替代的是使用的多接口方法。Python繼承問題中,最經典的一個問題是菱形繼承,然而菱形繼承是多繼承中的一個特殊的例子。從下圖中可以看到他們之間的差異和聯繫。後面將使用更加普遍的多繼承來說明這些繼承會遇到的特殊情況。
image

二、多繼承可能面臨的問題

從前面的介紹可以看到Python多繼承存在一些問題如下:

  • 在Class C1中調用的方法,在Class B1和Class B2有同樣的實現該如何選擇。
  • 多繼承的繼承順序問題。
  • 繼承過程中有super類名繼承這兩者有什麼問題。
  • 繼承過程中初始化的參數該如何申明
  • 繼承過程中想通過初始化類進行參數傳遞如何實現。
  • ...
    爲了針對上面出現的若干問題,將對繼承進行一些實驗來說明上述的問題,對於方法操作介紹的很多,這裏對屬性的操作進行描述。

三、多繼承實現的方法

在Python中多繼承可以通過兩種方法實現,第一個是類名繼承,第二個是super,具體如下:

3.1 類名繼承

類名繼承的簡單使用如下,注意:類名繼承的過程中會初始化兩次父類,這個問題可以通過super解決。

class A1:
    def __init__(self, a1):
        self.a1 = a1
        self.a_self = 8
        print(a1)


class B1(A1):
    def __init__(self, b1, *args):
        # A1.__init__(self, *args) # super繼承全參數傳遞
        A1.__init__(self, b1)
        self.b_self = 5
        self.a_self = 9
        print(b1)


class C1(A1):
    def __init__(self, c1, *args):
        # A1.__init__(self, *args) # super繼承全參數傳遞
        A1.__init__(self, c1)
        self.c_self = 7
        self.a_self = 10
        print(c1)


class D1(B1, C1):
    def __init__(self, d1, d2):
        C1.__init__(self, d1)
        B1.__init__(self, d2)
        # C1.__init__(self, d1)
        # super(D1, self).__init__(d1, d2) # super繼承

    def show(self):
        print(self.a_self)
        print(self.b_self)
        print(self.c_self)


d = D1("1", "2")
# 使用super繼承的結果是1,2
# 使用類名繼承的結果是1 1 2 2 A1被初始化了兩次
# d.show()
print(D1.__mro__)

3.2 super繼承

super繼承的簡單使用如下,值得注意的是在super下的多繼承需要使用*args進行參數傳遞保證初始化不會出現問題,並且可以解決類名繼承初始化兩次的問題。

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

    def show(self):
        print(self.a)
        print("A show()")


class B(A):
    def __init__(self, b1, b2, *args):
        print(b1, b2, "B")
        super(B, self).__init__(*args)
        self.b2 = b2
        self.b1 = b1
        self.a = 999

    def show(self):
        print(self.b2, self.b1, self.a)
        print("B show()")


class C(A):
    def __init__(self, c1, c2, *args):
        print(c1, c2, "C")
        super(C, self).__init__(*args)
        self.c2 = c2
        self.c1 = c1
        self.a = 9991

    def show(self):
        print("C show()")


class D(C, B):
    def __init__(self, d1, d2, d3, d4, d5):
        super(D, self).__init__(d1, d2, d3, d4, d5)
        print(d1)
        print(d2)
        self.d1 = d1
        self.d2 = d2
		
		
d = D(2, 3, 4, 5, 6)
print(d)
print(d.a)
d.show()
print(D.__mro__)
# 輸出結果
"""
2 3 C
4 5 B
6 A
2
3
<__main__.D object at 0x0000025B243C09C8>
9991
C show()
(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
"""

3.3 super繼承和類名繼承的性質

super繼承的性質:

  • 按照從左到右的順序繼承,先查詢自己有沒有方法,沒有方法按照從左到右的順序從父類中查詢,最後再去祖類查找。
  • 對於類屬性而言,同樣的道理,但是屬性在過程中可以進行操作,例如在A的中一個屬性爲10,在B中和C中分別加100,200,在傳給D時就變成了310。

對於第一點比較好理解,方法就是有就從自己往上搜索。在屬性上有一個點,就是我在A類申明瞭一個變量,通過類的不斷初始化,初始化(B,C)我可以將這個變量傳遞給D。這個時候就會出現一個情況,執行的結果是從祖類一步步遞歸下來。參考如下:

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

    def show(self):
        print(self, "A show()")


class B(A):
    def __init__(self, b1, b2, *args):
        super(B, self).__init__(*args)
        self.b1 = b1
        self.b2 = b2
        print("B", self.self_a)  # 1210
        self.self_a += 100
        print("B")

    def show(self):
        print(self, "B show()")


class C(A):
    def __init__(self, c1, c2, *args):
        super(C, self).__init__(*args)
        self.c1 = c1
        self.c2 = c2 
        print("C", self.self_a)  # 210
        self.self_a += 1000
        print("C")

    def show(self):
        print(self, "C show()")


class E(A):
    def __init__(self, *args):
        super(E, self).__init__(*args)
        self.self_a += 200
        print(self.show())  # none,因爲沒有初始化,不會執行到這個方法

    def show(self):
        print("E show()")


class D(B, C, E):
    def __init__(self, d1, d2, *args):
        super(D, self).__init__(*args)
        self.d1 = d1
        self.d2 = d2
        print("D")

    # def show(self):
    #     print(self)
    #     print("D show()")


d = D(1, 2, 3, 4, 5, 6, 7)
d.show()
print(d.self_a)
print(D.__mro__)
"""
A
<__main__.D object at 0x000001FEC09B0BA8> B show()
None
C 210
C
B 1210
B
D
<__main__.D object at 0x000001FEC09B0BA8> B show()
1310
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.A'>, <class 'object'>)
"""

對於上述的結果,有人可能會疑惑:不是先初始化B,在初始化C,所以B的結果是10。這是由於B,C中都沒有這個變量,因此它會一直按順序找,再去祖類中找,而這種執行方式是遞歸調用,由於B、C、E都沒有因此去了A,然後在E,C,B的遞歸調用,因此在B的時候結果是1210。

類名繼承的性質:

  • 不用傳遞全部參數
  • 初始化過程由自己控制

上述有一個問題,就是類名繼承是會初始化兩次的,因此在繼承順序中,後初始化的結果會把先計算的結果覆蓋掉,參考如下:

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

    def show(self):
        print(self, "A show()")


class B(A):
    def __init__(self, b1, b2, *args):
        # super(B, self).__init__(*args)
        A.__init__(self, 10)
        self.b1 = b1
        self.b2 = b2
        # print("B", self.self_a)
        print("B")
        self.self_a += 100

    def show(self):
        print(self, "B show()")


class C(A):
    def __init__(self, c1, c2, *args):
        # super(C, self).__init__(*args)
        A.__init__(self, 20)
        self.c1 = c1
        self.c2 = c2
        print("C")
        # self.self_a += 10000

    def show(self):
        print(self, "C show()")


class D(C, B):
    def __init__(self, d1, d2, *args):
        # super(D, self).__init__(*args)
        # C.__init__(self, 3, 5) # 先初始化C,不會覆蓋B的累加結果
        # print("C", self.self_a) # self_a的輸出結果爲110
        B.__init__(self, 1, 2)
        print("B", self.self_a) 
        C.__init__(self, 3, 5)  # 由於重新初始化會覆蓋B累加的結果
        print("C", self.self_a) # self_a的輸出結果爲10
        self.d1 = d1
        self.d2 = d2
        print("D")

    def show(self):
        print(self)
        print("D show()")


d = D(1, 2, 3, 4, 5, 6, 7)
# d.show()
print(D.__mro__)
print(d.self_a)
"""
A
B
B 110
A
C
C 10
D
(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
10  # 
"""

四、總結

super繼承和類名繼承的方法,會按照從自己出發逐步往父類走,走到第一個就算結束。對於變量而言,會遞歸進行,其順序和類名繼承的順序一致。而Python採取的是C3的繼承算法。注:雖然多繼承的問題可以通過逐步分析,以及調整代碼的順序解決問題,但是由於實際工程中其過於複雜,儘可能不使用,採取更加優化的解決方法爲好,並且通過設計模式和規範進行約束。例如將屬性私有化,不允許修改。

參考

博客園-Python的多繼承問題-MRO和C3算法

知乎-Python多重繼承問題-MRO和C3算法

CSDN-Python多繼承與super使用詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章