Python的面向對象編程(OOP)中,繼承是一個強大的工具,但它也常常是初學者(甚至有經驗的開發者)的困惑之源。核心的困惑點通常圍繞:

  1. 為什麼子類的 __init__ 必須手動調用父類的 __init__
  2. super() 到底是什麼,它和直接用父類名調用有何區別?

本文將通過“造房子”的類比和“鑽石問題”的深度解析,徹底澄清這些概念。


第1部分:核心問題——__init__的“覆蓋”機制

要理解為何需要 super(),首先必須理解Python的方法搜索規則

當在子類實例上調用一個方法時(如 __init__),Python的搜索順序是:

  1. 先在子類中尋找該方法。
  2. 如果找到了,立即停止搜索並執行。
  3. 如果沒找到,才去父類中尋找。

這個規則會導致一個常見問題:子類 __init__ 會覆蓋父類 __init__

類比:造房子(StandardHouse vs DeluxeHouse

假設我們有一個“標準房子”的設計圖(父類):

class StandardHouse:
    def __init__(self, foundation_type):
        # 建造房子的第一步:打地基
        self.foundation = foundation_type
        print(f"地基已打好,類型是: {self.foundation}")

    def get_info(self):
        print(f"這是一座標準房子,地基是 {self.foundation}")

現在,我們想設計一個“豪華房子”(子類),它在“標準房子”的基礎上,還想額外添加一個“游泳池”。為此,我們必須定義它自己__init__

錯誤的繼承(“覆蓋”導致的問題)
class DeluxeHouse(StandardHouse):
    # 子類定義了自己的 __init__
    def __init__(self, pool_size):
        # 建造房子的第一步:建游泳池
        self.pool = pool_size
        print(f"游泳池已建好,大小是: {self.pool} 平米")

# --- 嘗試建造 ---
print("--- 準備建造豪華房子 ---")
my_deluxe_house = DeluxeHouse(50)

# --- 嘗試獲取信息 ---
try:
    my_deluxe_house.get_info()  # 調用繼承來的方法
except AttributeError as e:
    print(f"\n!!! 出錯了: {e}")

執行結果:

--- 準備建造豪華房子 ---
游泳池已建好,大小是: 50 平米

!!! 出錯了: 'DeluxeHouse' object has no attribute 'foundation'

錯誤分析:

  1. 創建 DeluxeHouse(50) 時,Python 在 DeluxeHouse 中找到了 __init__
  2. 立刻停止搜索,並執行了子類的 __init__
  3. 父類 StandardHouse__init__ 從未被調用
  4. 因此,self.foundation(地基)這個屬性根本沒有被創建
  5. get_info() 試圖訪問 self.foundation 時,程序崩潰。

第2部分:解決方案——super()self的真相

DeluxeHouse 的建造者在建游泳池之前,必須顯式地(手動地)先去執行“標準房子”的建造流程(打地基)。super() 就是這個“手動調用父類”的命令。

1. super() 的含義
  • super():一個特殊的函數,返回一個“父類”的代理對象。
  • super().__init__(...):通過該代理對象,調用父類的 __init__ 方法。
2. self 的真相

從始至終,只有一個 self 對象。self 就是那個“正在被建造的實例”。

  • StandardHouse 的建造隊(父類)和 DeluxeHouse 的建造隊(子類)都在同一棟房子(self)上施工。
  • super() 就是子類團隊用來“呼叫”父類團隊的電話。
正確的代碼:
class DeluxeHouse(StandardHouse):
    # 子類 __init__ 必須接收 *所有* 參數(父類的 + 自己的)
    def __init__(self, foundation_type, pool_size):
        print("--- [子類] DeluxeHouse.__init__ 開始 ---")
        
        # 關鍵步驟:呼叫父類團隊,先把地基打好
        # 把屬於父類的參數 (foundation_type) 傳遞過去
        super().__init__(foundation_type)
        
        print("--- [子類] 父類工作已完成,現在我來添加新功能 ---")
        # 2. 子類現在可以安全地設置自己的屬性
        self.pool = pool_size
        print(f"    [子類] 游泳池已建好: {self.pool}")
        print("--- [子類] DeluxeHouse.__init__ 結束 ---")

# --- 嘗試建造 ---
my_deluxe_house = DeluxeHouse("鋼筋混凝土", 50)
my_deluxe_house.get_info()

執行流程分析:

  1. 調用 DeluxeHouse(...),進入 DeluxeHouse.__init__
  2. 執行 super().__init__(...)暫停子類 __init__跳轉到父類 StandardHouse.__init__
  3. 父類 __init__同一個 self 上設置了 self.foundation
  4. 父類 __init__ 結束,返回到子類 __init__
  5. 子類 __init__ 繼續執行,在同一個 self 上設置 self.pool
  6. self 對象現在同時擁有了 .foundation.pool

第3部分:super() vs ParentClass.__init__ (新舊對比)

既然必須手動調用父類,有兩種方法可以做到:

  1. 現代方式 (推薦): super().__init__(...)
  2. 老式方式 (不推薦): ParentClass.__init__(self, ...)

在簡單的“單繼承”場景下,兩者似乎都能工作:

class Vehicle:
    def __init__(self, brand):
        print(f"[Vehicle] 設置品牌為: {brand}")
        self.brand = brand

# 1. 現代方式 (super)
class Car_Modern(Vehicle):
    def __init__(self, brand, model):
        # 語法:
        # 1. 不需要寫父類名
        # 2. 不需要傳遞 self
        super().__init__(brand)
        self.model = model

# 2. 老式方式 (父類名)
class Car_Old(Vehicle):
    def __init__(self, brand, model):
        # 語法:
        # 1. 必須 明確寫出父類名 (Vehicle)
        # 2. 必須 手動傳遞 self
        Vehicle.__init__(self, brand)
        self.model = model

為什麼 super() 完勝?

特性

super().__init__(...) (現代)

ParentClass.__init__(self, ...) (老式)

self 參數

隱式傳遞。Python自動處理,代碼更簡潔。

必須手動傳遞。容易忘記,導致錯誤。

可維護性

高 (靈活)

低 (死板 / 易碎)

原因

如果父類改名 (如 Vehicle -> BaseVehicle),子類代碼無需任何改動

如果父類改名,你必須在所有子類中搜索並替換 Vehicle.__init__

多重繼承

唯一正確的方案

完全失效或導致災難性bug。


第4部分:決定性因素——多重繼承與“鑽石問題”

super() 存在的真正理由是:它能智能地處理複雜的多重繼承,而“父類名”調用則會徹底失敗。

經典的“鑽石問題”D 繼承自 BC,而 BC 都繼承自 A

A
   /   \
  B     C
   \   /
     D
1. super() 的正確演示

super() 會智能地遵循“方法解析順序”(MRO),確保繼承鏈中的每個 __init__ 都被調用一次,且僅被調用一次

class A:
    def __init__(self):
        print("A 初始化 (只應被調用一次)")

class B(A):
    def __init__(self):
        print("B 初始化開始...")
        super().__init__()
        print("B 初始化結束。")

class C(A):
    def __init__(self):
        print("C 初始化開始...")
        super().__init__()
        print("C 初始化結束。")

class D_Super(B, C): # 孫子 (使用 super)
    def __init__(self):
        print("D_Super 初始化開始...")
        super().__init__()
        print("D_Super 初始化結束。")

print("--- 測試 super() 的多重繼承 ---")
d1 = D_Super()

super() 的正確執行結果 (A只調用一次):

--- 測試 super() 的多重繼承 ---
D_Super 初始化開始...
B 初始化開始...
C 初始化開始...
A 初始化 (只應被調用一次)
C 初始化結束。
B 初始化結束。
D_Super 初始化結束。
2. “老式”方法的錯誤演示

如果 BC 使用“父類名”的方式調用 A

class B_Old(A):
    def __init__(self):
        print("B_Old 初始化開始...")
        A.__init__(self) # 明確調用 A
        print("B_Old 初始化結束。")

class C_Old(A):
    def __init__(self):
        print("C_Old 初始化開始...")
        A.__init__(self) # 明確調用 A
        print("C_Old 初始化結束。")

class D_Old(B_Old, C_Old):
    def __init__(self):
        print("D_Old 初始化開始...")
        B_Old.__init__(self) # 明確調用 B
        C_Old.__init__(self) # 明確調用 C
        print("D_Old 初始化結束。")

print("\n--- 測試 '父類名' 的多重繼承 (錯誤演示) ---")
d2 = D_Old()

“老式”方法的錯誤執行結果 (A被調用兩次!):

--- 測試 '父類名' 的多重繼承 (錯誤演示) ---
D_Old 初始化開始...
B_Old 初始化開始...
A 初始化 (只應被調用一次)  <-- A 被調用了第1次
B_Old 初始化結束。
C_Old 初始化開始...
A 初始化 (只應被調用一次)  <-- A 被調用了第2次 (!! BUG !!)
C_Old 初始化結束。
D_Old 初始化結束。

分析A (頂層父類) 的 __init__調用了兩次!如果 A__init__ 是在打開一個文件或建立一個數據庫連接,這種重複調用很可能會導致資源衝突或數據損壞。


結論與建議

對比項

super() (推薦)

ParentClass.__init__(self, ...) (不推薦)

簡單繼承

可用

可用(但不推薦)

多重繼承

完美工作

導致Bug(如重複調用)

可維護性

(父類改名不影響子類)

(父類改名必須重構所有子類)

語法

簡潔 (自動處理self)

繁瑣 (必須手動傳遞self)

適用範圍

現代Python (3.x) 的標準

歷史遺留 (Python 2 舊代碼)

最終建議:

始終使用 super() 它更簡潔、更靈活,是唯一能正確處理複雜繼承(多重繼承)的工具。ParentClass.__init__ 是一種需要被理解(以便能看懂舊代碼)但不再需要被使用的歷史寫法。