一、菱形繼承與多繼承
Python在繼承方面與Java不同,Python可以進行多繼承,而Java則不能多繼承,替代的是使用的多接口方法。Python繼承問題中,最經典的一個問題是菱形繼承,然而菱形繼承是多繼承中的一個特殊的例子。從下圖中可以看到他們之間的差異和聯繫。後面將使用更加普遍的多繼承來說明這些繼承會遇到的特殊情況。
二、多繼承可能面臨的問題
從前面的介紹可以看到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的繼承算法。注:雖然多繼承的問題可以通過逐步分析,以及調整代碼的順序解決問題,但是由於實際工程中其過於複雜,儘可能不使用,採取更加優化的解決方法爲好,並且通過設計模式和規範進行約束。例如將屬性私有化,不允許修改。