【 Python 】繼承並沒有你想得那麼簡單 . 深入理解 super() – mro

在實作 Strategy Pattern 的過程中想要做更複雜一點的範例,碰巧發現了 Python 繼承大家常用的 super function 其實並不是繼承的意思,今天就跟大家分享到底 super 是怎麼運作的吧!這篇會講解到: 一般 Python 的繼承方法、多重繼承、mro、super 的重要性

前言:

其實這算是為了之後的文章做講解。 這陣子在研究 Design Pattern 使用 Python 的作法 ( 看首頁旁邊的推薦書籍就會發現蹊翹了XD,我看過的書覺得不錯的就會推薦給大家看~) 在實作第一章(Strategy Pattern)的過程中想要做更複雜一點的範例,碰巧發現了 Python 繼承大家常用的 super function 其實並不是繼承的意思,今天就跟大家分享到底 super 是怎麼運作的吧!

這篇會講解到:

  • 一般 Python 的繼承方法
  • 多重繼承
  • mro
  • super 的重要性

一般 Python 的繼承方法:

我們用一個蠻常見的例子來舉例 我們創一個交通工具的基礎 Class 吧!

Transportation: 交通工具基礎 Class

1class Transportation:
2    def __init__(self):
3        print("Transportation Init")
4    def __repr__(self):
5        return f""

接下來我們來新增 Transportation 的子類別吧! 我想 Car 也是交通工具的一種,就用它來表示吧!

 1class Transportation:
 2    def __init__(self):
 3        print("Transportation Init")
 4    def __repr__(self):
 5        return f""
 6
 7class Car(Transportation):
 8    def __init__(self, brend: str):
 9        super().__init__()  # 這一行決定會不會執行到 繼承的Class 的 Init
10        self.brend = brend
11
12    def __repr__(self):
13        return f""
14
15car = Car("XD")  # XD 牌汽車 XDDD
16print(car)  # 

你可以複製下來跑跑看就會發現跑出來的會是像下面這樣。 ( 因為左右角括號在這裡不能顯示… 所以我就把他先拿掉了)

1Transportation Init
2Car 品牌: XD

不知道大家有沒有注意 code 旁邊的註解 super().init() # 這一行決定會不會執行到 繼承的Class 的 Init 所以這樣看來 super 就是代表 父類的Class 嗎?!很多人會這樣以為,但是其實不是!只是在簡單的例子中剛好就代表著 父類的Class,所以大家才會這樣以為! 為了更好理解,我就用複雜一點的例子來舉例就會發現問題了!

多重繼承:

我先將剛剛的 code 多加上一個 Class 讓它複雜一點

我們來多繼承一個 Vehicle 類別吧!

1class Vehicle:
2    def __init__(self):
3        print("Vehicle Init")
4    def __repr__(self):
5        return f""
 1class Transportation:
 2    def __init__(self):
 3        print("Transportation Init")
 4    def __repr__(self):
 5        return f""
 6
 7class Vehicle:
 8    def __init__(self):
 9        print("Vehicle Init")
10    def __repr__(self):
11        return f""
12
13class Car(Transportation, Vehicle):
14    def __init__(self, brend: str):
15        super().__init__()  # 這一行決定會不會執行到 繼承的Class 的 Init
16        self.brend = brend
17
18    def __repr__(self):
19        return f""
20
21car = Car("XD")  # XD 牌汽車 XDDD
22print(car)  # 

執行後會發現只有一行輸出而已,卻沒有 Vehicle Init 的字樣出現! 這是為什麼呢!

這是因為在 Python 當中,繼承的順序在一開始就決定好了,也就是當一個類別繼承多個類別的時候(多重繼承),下一個類別 init 的類別已經定好了,而這個繼承的順序是由一種叫做「C3 線性化算法」的演算法去決定的!

我們不需要知道太底層的演算法,只要記得,在撰寫 class 的時候越靠近左括號的類別會越先執行繼承就好了!

但是我們還是沒有討論到「 為什麼沒執行 print("Vehicle Init") 」這行!

那是因為當我們在 Car.__init__() 中呼叫到 super().__init__() ,而 super().__init__() 代表的是下一個繼承的類別的 __init__(),但是我們繼承了兩個類別,卻只呼叫到一次 super() 也就代表說我們只會讓一個繼承的類別,也就是 Transportation

讓指定繼承的類別 Init

想要讓指定的繼承類別初始化可以用 類別.__init__() 的方式

1class Car(Transportation, Vehicle):
2    def __init__(self, brend: str):
3        Vehicle.__init__()
4        self.brend = brend

或是可以一口氣讓所有的繼承類別都一起 Init

透過讓每個類別都呼叫 super().init() 來讓每一個 mro 中的類別都呼叫下一個 mro 順位的類別的 init()

 1class Transportation:
 2    def __init__(self):
 3        super().__init__() # 讓下一個 mro 中的類別初始化
 4        print("Transportation Init")
 5    def __repr__(self):
 6        return f""
 7
 8class Vehicle:
 9    def __init__(self):
10        super().__init__() # 讓下一個 mro 中的類別初始化
11        print("Vehicle Init")
12    def __repr__(self):
13        return f""
14
15class Car(Transportation, Vehicle):
16    def __init__(self, brend: str):
17        super().__init__()  # 這一行決定會不會執行到 繼承的Class 的 Init
18        self.brend = brend
19
20    def __repr__(self):
21        return f""
22
23car = Car("XD")  # XD 牌汽車 XDDD
24print(car)  # 

結語

mro (method resolution order) 是一個 Python 蠻底層的概念,這對開發者來說也很重要,知道了 mro 的順序就可以讓 多層繼承 或是 多重繼承 的類別照我們想要的順序去初始化,也可以避免菱形繼承的重複初始化問題!

好啦! Python mro 概念介紹到這裡! 下一篇我來跟大家聊聊 不理解 Python mro 的話會寫出怎麼樣的重複初始化的 code 範例!

All rights reserved,未經允許不得隨意轉載
使用 Hugo 建立
主題 StackJimmy 設計