前言
本文主要做科普用,在真實編程中不建議使用多重繼承,或者少用多重繼承,避免使代碼難以理解。
方法解析順序(MRO)
關(guān)于多重繼承,比較重要的是它的方法解析順序(可以理解為類的搜索順序),即MRO。這個跟類是新式類還是經(jīng)典類有關(guān),因為兩者的搜索算法不同。
在Python2及以前的版本,由任意內(nèi)置類型派生出的類(只要一個內(nèi)置類型位于類樹的某個位置),都屬于新式類;反之,不由任意內(nèi)置類型派生出的類,則稱之為經(jīng)典類
在Python3以后,沒有該區(qū)分,所有的類都派生自內(nèi)置類型object,不管有沒有顯式繼承object,都屬于新式類。
對于經(jīng)典類,多重繼承的MRO是深度優(yōu)先,即從下往上搜索;新式類的MRO是采用C3算法(不同情況下,可表現(xiàn)為廣度優(yōu)先,也可表現(xiàn)為深度優(yōu)先)。
C3算法表現(xiàn)為深度優(yōu)先的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# C3-深度優(yōu)先(D -> B -> A -> C) class A: var = 'A var' class B(A): pass class C: var = 'C var' class D(B, C): pass if __name__ == '__main__': # [< class '__main__.D'>, < class '__main__.B'>, < class '__main__.A'>, < class '__main__.C'>, < class 'object'>] print(D.mro()) # A var print(D.var) 復(fù)制代碼 |
C3算法表現(xiàn)為廣度優(yōu)先的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# C3-廣度優(yōu)先(D -> B -> C -> A) class A: var = 'A var' class B(A): pass class C(A): var = 'C var' class D(B, C): pass if __name__ == '__main__': # [< class '__main__.D'>, < class '__main__.B'>, < class '__main__.C'>, < class '__main__.A'>, < class 'object'>] print(D.mro()) # C var print(D.var) 復(fù)制代碼 |
注:關(guān)于C3的詳細(xì)算法本文不討論,因為我也搞不懂(狗頭保命)
菱形多重繼承
其實菱形多重繼承上面已經(jīng)有例子了,就是C3算法表現(xiàn)為廣度優(yōu)先
這個例子,畫起圖來是這樣的:

菱形多重繼承會導(dǎo)致的一個問題是A初始化了兩次,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
class A: def say(self): print("A say") class B(A): def say(self): print("B say") A.say(self) class C(A): def say(self): print("C say") A.say(self) class D(B, C): def say(self): print("D say") B.say(self) C.say(self) if __name__ == '__main__': dd = D() dd.say() 復(fù)制代碼 |
如果只想調(diào)用一次A,可使用super方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
class A: def say(self): print("A say") class B(A): def say(self): print("B say") super().say() class C(A): def say(self): print("C say") super().say() class D(B, C): def say(self): print("D say") super().say() if __name__ == '__main__': print(D.mro()) dd = D() dd.say() 復(fù)制代碼 |
_init_ 與 super()
1.如果父類有init方法,子類沒有,則子類默認(rèn)繼承父類的init方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class A: def __init__(self, a1, a2): self.a1 = a1 self.a2 = a2 def say(self): print("A say, a1: %s, a2: %s" % (self.a1, self.a2)) class B(A): def say(self): print("B say, a1: %s, a2: %s" % (self.a1, self.a2)) if __name__ == '__main__': # 因為B繼承了A的init方法,所以也要傳入 a1,a2參數(shù) bb = B("10", "100") bb.say() 復(fù)制代碼 |
2.如果父類有init方法,子類也有,可理解為子類重寫了父類的init方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class A: def __init__(self, a1, a2): self.a1 = a1 self.a2 = a2 def say(self): print("A say, a1: %s, a2: %s" % (self.a1, self.a2)) class B(A): def __init__(self, b1): self.b1 = b1 def say(self): print("B say, b1: %s" % self.b1) if __name__ == '__main__': # 此處B重寫了A的init方法,所以只需要傳入b1參數(shù),也沒有擁有a1,a2屬性 bb = B("10") bb.say() 復(fù)制代碼 |
3.對于第二點,為了能使用或者擴展父類的行為,更常見的做法是在重寫init方法的同時,顯示調(diào)用父類的init方法(意味傳的參數(shù)要變成3個)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# 三種寫法 class A: def __init__(self, a1, a2): self.a1 = a1 self.a2 = a2 def say(self): print("A say, a1: %s, a2: %s" % (self.a1, self.a2)) class B(A): def __init__(self, b1, a1, a2): # 第一種寫法: Python2的寫法 # super(B, self).__init__(a1, a2) # 第二種寫法(推薦):Python3的寫法,與第一種等價 super().__init__(a1, a2) # 第三種寫法:與前兩種等價,不過這種需要顯示調(diào)用基類,而第二種不用 # A.__init__(self, a1, a2) self.b1 = b1 def say(self): print("B say, a1: %s, a2: %s, b1: %s" % (self.a1, self.a2, self.b1)) if __name__ == '__main__': # 此處B重寫了A的init方法,所以只需要傳入b1參數(shù),也沒有擁有a1,a2屬性 bb = B("10", "100", "1000") bb.say() 復(fù)制代碼 |
最后的提醒
注意 __init__
方法不要寫錯,避免寫成__ini__
或者其他的,因為兩個是在太像,出了問題很難排查(坑過兩次)。
參考
python 多重繼承的事
當(dāng)心掉進Python多重繼承里的坑