Python MRO 完全攻略:當兒子繼承了多個富爸爸,該聽誰的?—— 深入理解 Method Resolution Order (MRO) 與 C3 線性化算法

加入好友
加入社群
Python MRO 完全攻略:當兒子繼承了多個富爸爸,該聽誰的?—— 深入理解 Method Resolution Order (MRO) 與 C3 線性化算法 - 儲蓄保險王

在 Python 的物件導向程式設計(OOP)中,
繼承(Inheritance) 是一個核心概念。
當我們只繼承一個父類別時,世界很單純。
但當一個類別同時繼承多個父類別(多重繼承)時,問題就來了:

「如果爸爸有錢,媽媽也有錢,那我到底是繼承爸爸的,還是媽媽的?」

這就是 MRO (Method Resolution Order) 出場的時候。
它是 Python 用來決定 「搜尋方法順序」 的一套交通規則。

本篇教程將涵蓋:

  1. 什麼是 MRO? 為什麼我們需要它?
  2. 單一繼承 vs 多重繼承:從簡單的家譜到複雜的繼承網。
  3. 經典難題:鑽石繼承 (Diamond Problem)
  4. 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 物件的始祖
Python MRO 完全攻略:當兒子繼承了多個富爸爸,該聽誰的?—— 深入理解 Method Resolution Order (MRO) 與 C3 線性化算法 - 儲蓄保險王

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__]}")
Python MRO 完全攻略:當兒子繼承了多個富爸爸,該聽誰的?—— 深入理解 Method Resolution Order (MRO) 與 C3 線性化算法 - 儲蓄保險王

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 演算法 來解決這個問題,確保:

  1. 子類別永遠在父類別之前。
  2. 父類別的順序保持在宣告時的順序 (B 在 C 前)。
  3. 共同的祖先 (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__}")
Python MRO 完全攻略:當兒子繼承了多個富爸爸,該聽誰的?—— 深入理解 Method Resolution Order (MRO) 與 C3 線性化算法 - 儲蓄保險王

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
Python MRO 完全攻略:當兒子繼承了多個富爸爸,該聽誰的?—— 深入理解 Method Resolution Order (MRO) 與 C3 線性化算法 - 儲蓄保險王

結論

  1. MRO 是一張地圖:Python 在這張地圖上尋找方法,找到了就停下來(除非你用 super() 叫它繼續找)。
  2. 左側優先class Sub(A, B) 中,A 比 B 優先。
  3. 晚輩優先:子類別永遠比父類別優先。
  4. 共同祖先墊底:在鑽石繼承中,共同的祖先會排在所有支線都走完之後,避免過早被呼叫。
  5. 善用 inspect:遇到鬼打牆的繼承問題時,印出 .__mro__ 就能看清真相。

掌握 MRO,你就掌握了 Python 物件導向最複雜也最強大的部分!

推薦hahow線上學習python: https://igrape.net/30afN

加入好友
加入社群
Python MRO 完全攻略:當兒子繼承了多個富爸爸,該聽誰的?—— 深入理解 Method Resolution Order (MRO) 與 C3 線性化算法 - 儲蓄保險王

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *