在 Python 的物件導向程式設計(OOP)中,
繼承(Inheritance) 是一個核心概念。
當我們只繼承一個父類別時,世界很單純。
但當一個類別同時繼承多個父類別(多重繼承)時,問題就來了:
「如果爸爸有錢,媽媽也有錢,那我到底是繼承爸爸的,還是媽媽的?」
這就是 MRO (Method Resolution Order) 出場的時候。
它是 Python 用來決定 「搜尋方法順序」 的一套交通規則。
本篇教程將涵蓋:
- 什麼是 MRO? 為什麼我們需要它?
- 單一繼承 vs 多重繼承:從簡單的家譜到複雜的繼承網。
- 經典難題:鑽石繼承 (Diamond Problem)。
super()函數的魔術:它是如何沿著 MRO 鏈條傳遞的。
1. 暖身:單一繼承的單純世界
在單一繼承中,MRO 非常直觀:自己 -> 爸爸 -> 阿公 -> … -> Object。
我們可以用 ClassName.__mro__ 來查看這個順序。
class Grandpa:
def say_hi(self):
print("👴 Grandpa: 乖孫你好")
class Dad(Grandpa):
# Dad 沒有覆寫 say_hi
pass
class Son(Dad):
def say_hi(self):
print("👦 Son: 嗨!")
# 建立實例
s = Son()
s.say_hi()
# 查看 MRO
print("\n[Son 的繼承順序 MRO]")
for cls in Son.__mro__:
print(cls)
# output 解讀:
# 1. 雖然 Grandpa 也有 say_hi,但在 Son 自己就找到了,所以印出 "Son: 嗨!"
# 2. MRO 順序最後一定是 <class 'object'>,這是所有 Python 物件的始祖。
2. 進階:多重繼承的兩難
當一個子類別同時繼承兩個父類別,而這兩個父類別都有同名的方法時,Python 該選誰?
規則是:由左至右 (Left-to-Right)。
寫在括號左邊的父類別,優先權較高。class Child(Father, Mother): -> Father 優先class Child(Mother, Father): -> Mother 優先
class Father:
def wealth(self):
print("👨 Father: 我有 100 萬美金")
class Mother:
def wealth(self):
print("👩 Mother: 我有 5000 萬台幣")
# 情境 A: 爸爸優先
class Child_A(Father, Mother):
pass
# 情境 B: 媽媽優先
class Child_B(Mother, Father):
pass
print("=== Child A (Father First) ===")
a = Child_A()
a.wealth()
print(f"MRO: {[c.__name__ for c in Child_A.__mro__]}")
print("\n=== Child B (Mother First) ===")
b = Child_B()
b.wealth()
print(f"MRO: {[c.__name__ for c in Child_B.__mro__]}")
3. 大魔王:鑽石繼承 (The Diamond Problem)
這是多重繼承中最著名的問題。
如果 D 繼承 B 和 C,而 B 和 C 都繼承 A。
這時候形成了一個菱形(鑽石形狀)。
A
/ \
B C
\ /
D
問:如果 D 呼叫了一個方法,順序該是 D -> B -> A -> C 嗎?
❌ 錯! 如果這樣走,A 會被呼叫兩次(一次經由 B,一次經由 C),而且 C 可能會被 A 覆蓋掉(如果 A 在 C 之前被呼叫)。
Python 使用 C3 Linearization 演算法 來解決這個問題,確保:
- 子類別永遠在父類別之前。
- 父類別的順序保持在宣告時的順序 (B 在 C 前)。
- 共同的祖先 (A) 只有在所有子孫 (B, C) 都被搜尋完之後,才會出現。
正確順序是:D -> B -> C -> A。
class A:
def show(self):
print("A: 我是祖先")
class B(A):
def show(self):
print("B: 我是左邊的路")
class C(A):
def show(self):
print("C: 我是右邊的路")
class D(B, C):
pass
d = D()
d.show()
print("\n=== D 的 MRO (鑽石結構) ===")
# 觀察重點:C 雖然是第二順位繼承,但它排在 A 之前!
# 這保證了 C 的邏輯不會被「更古老」的 A 意外覆蓋。
for i, cls in enumerate(D.__mro__):
print(f"{i}. {cls.__name__}")
4. super() 到底是什麼?
很多人誤以為 super() 只是「呼叫父類別」。這在單一繼承是對的,但在多重繼承中,它是 「呼叫 MRO 清單中的下一位」。
這就是為什麼在鑽石結構中,我們可以用 super() 串起所有人的 __init__,而且保證每個人只被執行一次。
下面的例子展示:即使 Left 的父類別是 Base,但因為 MRO 的關係,它的 super() 竟然呼叫到了 Right!這就是 MRO 的魔法。
class Base:
def action(self):
print(" -> Base.action 被執行")
class Left(Base):
def action(self):
print(" -> Left.action 開始")
super().action() # 這裡的 super() 會是誰?在 Diamond 中,它可能不是 Base!
print(" -> Left.action 結束")
class Right(Base):
def action(self):
print(" -> Right.action 開始")
super().action()
print(" -> Right.action 結束")
class Diamond(Left, Right):
def action(self):
print("-> Diamond.action 開始")
super().action()
print("-> Diamond.action 結束")
print("=== MRO 預測 ===")
print("預期順序: Diamond -> Left -> Right -> Base")
print(f"實際 MRO: {[c.__name__ for c in Diamond.__mro__]}")
print("\n=== 執行結果 ===")
d = Diamond()
d.action()
# 驚奇點:
# 注意看 Left 的 super().action() 執行後,跳出來的竟然是 "Right.action"!
# 這是因為在 Diamond 的 MRO 中,Left 的下一棒是 Right。
結論
- MRO 是一張地圖:Python 在這張地圖上尋找方法,找到了就停下來(除非你用
super()叫它繼續找)。 - 左側優先:
class Sub(A, B)中,A 比 B 優先。 - 晚輩優先:子類別永遠比父類別優先。
- 共同祖先墊底:在鑽石繼承中,共同的祖先會排在所有支線都走完之後,避免過早被呼叫。
- 善用
inspect:遇到鬼打牆的繼承問題時,印出.__mro__就能看清真相。
掌握 MRO,你就掌握了 Python 物件導向最複雜也最強大的部分!
推薦hahow線上學習python: https://igrape.net/30afN


![Python: pandas.DataFrame串接; pandas.concat( [df1,df2] , axis=1, ignore_index=True) ; .append() 產生一個新的DataFrame; 插入欄 .insert() 改變原DataFrame Python: pandas.DataFrame串接; pandas.concat( [df1,df2] , axis=1, ignore_index=True) ; .append() 產生一個新的DataFrame; 插入欄 .insert() 改變原DataFrame](https://i1.wp.com/savingking.com.tw/wp-content/uploads/2022/11/20221129145451_29.png?quality=90&zoom=2&ssl=1&resize=350%2C233)







近期留言