python 繼承機制研究

今天遇到了一個 python 程式語言繼承機制的問題,如以下程式碼所示,可以發現子類別 SuperMan 同時繼承了兩個父類別 WomanMan,而且這兩個父類別中都有一個叫 foo 的方法,那 SuperMan 到底會繼承哪一個並呼叫呢 ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Woman:
@staticmethod
def foo():
print("I am a woman")

class Man:
@staticmethod
def foo():
print("I am man")

class SuperMan(Woman, Man):
pass

SuperMan.foo()

執行結果如以下,答案是會先呼叫在類別繼承括號中最左邊的的 Woman 類別的方法foo。若把兩個父類別順序對換,則這次會先從 Man開始執行。

1
I am a woman

而為什麼會這樣執行而不會出錯呢?這個背後的機制就是 python 的繼承順序機制演算法 MRO(Method Resolution Order)定義了繼承的走訪順序,執行以下的程式碼可以看到 MRO 裡面的類別繼承內容。

1
print(SuperMan.mro())

可以發現從最左邊 SuperMan 自己開始,後一個類別就是 WomanManobject,這裏要注意的是 python 的類別也是一種物件,因此繼承鍊最後也會指向到object

1
[<class '__main__.SuperMan'>, <class '__main__.Woman'>, <class '__main__.Man'>, <class 'object'>]

而 MRO 的繼承順序行為背後是有一個 python 的 C3 演算法來實作的,C3 演算法是 python3 的繼承順序機制,這個演算法改進了過去 python2 繼承演算法的問題,結合了 DFS 與 BFS的優缺點,關於 MRO 的詳細過往可以參考這篇文章的介紹
接著修改了以上的程式如下,在 WomanMan 之上又共同繼承了Human 類別,形成了一個棱形繼承的結構,那麼繼承順序會變得如何呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Human:
@staticmethod
def foo():
print("I am a human")

class Woman(Human):
@staticmethod
def foo():
print("I am a woman")

class Man(Human):
@staticmethod
def foo():
print("I am man")

class SuperMan(Woman, Man):
pass

SuperMan.foo()
print(SuperMan.mro())

程式執行結果如下:

1
2
I am a woman
[<class '__main__.SuperMan'>, <class '__main__.Woman'>, <class '__main__.Man'>, <class '__main__.Human'>,<class 'object'>]

如果是在棱形繼承的結構之下,MRO 的行為會類似 BFS ,從左邊開始以廣度優先的順序來繼承節點。

然而在不是棱形繼承的結構,而是一般的繼承結構又會如何呢? 這裏更改以上的程式如下,移除 ManHuman 的繼承關係。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Human:
@staticmethod
def foo():
print("I am a human")

class Woman(Human):
@staticmethod
def foo():
print("I am a woman")

class Man:
@staticmethod
def foo():
print("I am man")

class SuperMan(Woman, Man):
pass

SuperMan.foo()
print(SuperMan.mro())

程式的執行結果會改變如下:

1
2
I am a woman
[<class '__main__.SuperMan'>, <class '__main__.Woman'>, <class '__main__.Human'>, <class '__main__.Man'>,<class 'object'>]

可以發現繼承的行為改變了!原本繼承 Woman 之後繼承 Man 現在卻變成了繼承 HumanMRO 在非棱形的繼承結構的行為會與 DFS 相同,變成從左到右以深度優先的方式來繼承節點。

reference:
https://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path
https://www.python-course.eu/python3_multiple_inheritance.php
https://read01.com/kRkykj.html