Python 物件導向程式設計(OOP)
主題:super()
簡介
在 Python 的類別繼承(inheritance)中,子類別常需要呼叫父類別的建構子或其他方法,以保留父類別已實作的行為,同時再加入自己的擴充。
如果直接以 ParentClass.__init__(self, ...) 的方式呼叫,會產生 硬編碼(hard‑coding)的問題:當類別層級變動或多重繼承(multiple inheritance)時,程式碼很容易出錯或難以維護。
super() 正是為了解決這類問題而設計的 內建函式。它會自動搜尋 方法解析順序(Method Resolution Order, MRO),找出正確的父類別,並以正確的方式呼叫對應的方法。掌握 super() 的用法,能讓你的類別設計更具彈性、可讀性更佳,也更符合 Pythonic 的編程風格。
核心概念
1. 為什麼需要 super()?
- 避免硬編碼:不必寫死父類別名稱,子類別改名或改層級時不必修改呼叫程式碼。
- 支援多重繼承:在菱形繼承(diamond inheritance)結構中,
super()會依照 MRO 只呼叫一次每個父類別的方法,避免重複執行。 - 提升可維護性:使用
super()的類別更易於重構與測試。
2. super() 的基本語法
super().method_name(args)
- Python 3 允許省略
super()的兩個參數(type與obj),由解譯器自動推斷。 - 在 Python 2 必須寫成
super(CurrentClass, self).method_name(args)。
3. 方法解析順序(MRO)
每個類別都有一個 __mro__ 屬性,儲存類別的搜尋順序。例如:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>,
# <class '__main__.C'>, <class '__main__.A'>,
# <class 'object'>)
super() 會依照這個順序尋找下一個實作的方法。
程式碼範例
以下提供 5 個實用範例,從最簡單的單一繼承到複雜的多重繼承,說明 super() 的正確使用方式與注意事項。
範例 1️⃣:單一繼承中的建構子呼叫
class Animal:
def __init__(self, name):
self.name = name
print(f"[Animal] 初始化 {self.name}")
class Dog(Animal):
def __init__(self, name, breed):
# 使用 super() 呼叫父類別的 __init__
super().__init__(name)
self.breed = breed
print(f"[Dog] 品種是 {self.breed}")
# 測試
d = Dog("Buddy", "Golden Retriever")
執行結果
[Animal] 初始化 Buddy
[Dog] 品種是 Golden Retriever
重點:
super().__init__(name)讓Dog不必知道父類別的名稱是Animal,若日後改名或改層級,程式仍能正常運作。
範例 2️⃣:多層次繼承(Grandparent → Parent → Child)
class Vehicle:
def start(self):
print("Vehicle 啟動")
class Car(Vehicle):
def start(self):
print("Car 準備啟動")
super().start() # 呼叫 Vehicle.start
class SportsCar(Car):
def start(self):
print("SportsCar 引擎點火")
super().start() # 呼叫 Car.start
# 測試
s = SportsCar()
s.start()
執行結果
SportsCar 引擎點火
Car 準備啟動
Vehicle 啟動
說明:每層都只負責自己的部份,
super()依照 MRO 依序向上呼叫,讓程式碼保持 單一職責(Single Responsibility)。
範例 3️⃣:多重繼承(菱形繼承)與 super()
class A:
def greet(self):
print("Hello from A")
class B(A):
def greet(self):
print("Hello from B")
super().greet()
class C(A):
def greet(self):
print("Hello from C")
super().greet()
class D(B, C):
def greet(self):
print("Hello from D")
super().greet()
# 測試
d = D()
d.greet()
print("MRO:", D.__mro__)
執行結果
Hello from D
Hello from B
Hello from C
Hello from A
MRO: (<class '__main__.D'>, <class '__main__.B'>,
<class '__main__.C'>, <class '__main__.A'>,
<class 'object'>)
關鍵:即使
A被B、C同時繼承,super()只會呼叫一次A.greet,避免重複執行。
範例 4️⃣:在非建構子(ordinary method)中使用 super()
class LoggerMixin:
def log(self, message):
print(f"[LOG] {message}")
class Service(LoggerMixin):
def process(self, data):
self.log(f"Processing {data}")
# 其他業務邏輯...
print("處理完成")
svc = Service()
svc.process("使用者資料")
執行結果
[LOG] Processing 使用者資料
處理完成
說明:
super()不僅限於__init__,任何方法皆可使用,以 混入(Mixin) 方式擴充功能,保持程式碼的 可重用(reusability)。
範例 5️⃣:super() 搭配 __new__(自訂物件建立)
class MyInt(int):
def __new__(cls, value):
print("在 __new__ 中自訂建立過程")
# 必須使用 super().__new__ 產生真正的 int 物件
obj = super().__new__(cls, value)
return obj
def __init__(self, value):
# __init__ 在此僅作為示範,int 本身不需要額外初始化
print(f"在 __init__ 中接收到 {value}")
# 測試
num = MyInt(42)
print(num, type(num))
執行結果
在 __new__ 中自訂建立過程
在 __init__ 中接收到 42
42 <class '__main__.MyInt'>
要點:
__new__必須返回一個實例,若忘記呼叫super().__new__,將無法正確建立基底類別的內建型別。
常見陷阱與最佳實踐
| 常見問題 | 說明 | 解決方式 |
|---|---|---|
忘記加 super() |
直接呼叫父類別方法會硬編碼,且在多重繼承時會重複呼叫。 | 使用 super().method(),讓 MRO 決定呼叫目標。 |
在 __init__ 內未傳遞所有參數 |
父類別需要的參數若未傳遞,會拋出 TypeError。 |
確認 super().__init__(**kwargs) 或使用 關鍵字參數 透過 *args, **kwargs 轉遞。 |
在 __new__ 中忘記回傳物件 |
__new__ 必須回傳一個實例,否則會得到 None。 |
obj = super().__new__(cls, *args, **kwargs),最後 return obj。 |
混用舊式 super(Class, self) 與新式 super() |
兩者行為相同,但寫法不一致會降低可讀性。 | 統一使用 Python 3 的簡寫 super()。 |
在靜態方法(@staticmethod)中使用 super() |
靜態方法沒有 self,無法取得 MRO。 |
若需要呼叫父類別的靜態方法,直接使用類別名稱;或改為 類別方法(@classmethod),再使用 super()。 |
最佳實踐
- 盡量在所有可覆寫的方法中使用
super(),即使目前只有單一繼承,未來擴充時也不會出問題。 - 使用
*args, **kwargs轉遞參數,保持子類別與父類別的介面彈性。 - 在多重繼承時,確保每個父類別都使用
super(),如此才能形成「協同」的呼叫鏈。 - 避免在
__init__內執行副作用(例如 I/O),因為super()可能會被多次呼叫(如在菱形繼承中)。將副作用搬到獨立方法或屬性初始化中。 - 對於混入(Mixin)類別,只實作單一職責的方法,並使用
super()讓其他混入也能參與呼叫鏈。
實際應用場景
1. Web 框架的請求處理管線(Middleware)
在 Django、Flask 等框架中,開發者常會自訂 中間件(middleware) 來前置或後置處理請求。若每個中間件都使用 super() 呼叫下一層,就能形成 可插拔 的處理鏈,且不必擔心中間件的排列順序。
class BaseMiddleware:
def process_request(self, request):
# 預設什麼都不做
pass
class AuthMiddleware(BaseMiddleware):
def process_request(self, request):
print("驗證使用者")
super().process_request(request)
class LoggingMiddleware(BaseMiddleware):
def process_request(self, request):
print("記錄請求")
super().process_request(request)
# 組合
class AppMiddleware(LoggingMiddleware, AuthMiddleware):
pass
req = {}
AppMiddleware().process_request(req)
輸出
記錄請求
驗證使用者
2. GUI 元件的多層次初始化
在 PyQt、Tkinter 這類 GUI 框架,子類別往往需要先呼叫父類別的建構子以建立底層視窗資源,再加入自訂的佈局或事件。使用 super() 能保證 跨平台 與 未來框架升級 時的相容性。
3. 資料模型的多重繼承(Mixin)
常見的 ORM(如 SQLAlchemy)會提供 TimestampMixin、SoftDeleteMixin 等混入類別。透過 super(),每個 mixin 都能在 __init__ 中自動設定欄位,而不必擔心呼叫順序。
class TimestampMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.created_at = datetime.now()
self.updated_at = datetime.now()
class SoftDeleteMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.is_deleted = False
class User(TimestampMixin, SoftDeleteMixin, BaseModel):
pass
4. 測試雙倍(Test Double)與 Mock
在單元測試中,常會建立 子類別 來覆寫部份行為,同時仍希望保留原本的邏輯。super() 讓測試子類別可以在需要時呼叫真實實作,提升測試的真實度。
總結
super() 是 Python 物件導向程式設計中不可或缺的工具,它能:
- 自動依照 MRO 找到正確的父類別,避免硬編碼。
- 支援多重繼承,確保每個父類別的行為只被呼叫一次。
- 提升程式碼彈性與可維護性,讓未來的類別重構更安全。
在實務開發中,從 建構子、普通方法、Mixin、到 __new__,只要遵循「每層都使用 super()」的原則,就能建立協同、可擴充的類別層次結構。記得配合 *args, **kwargs 轉遞參數、在多重繼承時保持每個父類別都實作 super(),並留意靜態方法與特殊方法的限制,這樣就能在各種應用場景(Web 框架、GUI、ORM、測試)中自如運用 super(),寫出更 Pythonic、更具可讀性的程式碼。祝你在 Python OOP 的旅程中玩得開心、寫得順手!