本文 AI 產出,尚未審核

Python 課程 – 物件導向程式設計(OOP)

主題:方法覆寫(Method Overriding)


簡介

在物件導向程式設計(OOP)裡,類別(class) 讓我們可以把資料與行為封裝在一起,而繼承(inheritance) 則提供了「類別之間的關係」與「程式碼重用」的機制。當子類別(sub‑class)繼承父類別(super‑class)的屬性與方法後,常會遇到「同名方法需要不同實作」的情況,這時就會用到 方法覆寫(method overriding)

方法覆寫不僅是語法層面的技巧,更是設計上「多型(polymorphism)」的核心概念。透過覆寫,我們可以讓子類別在呼叫同一個方法時,呈現出符合自身需求的行為,從而提升程式的彈性、可讀性與可維護性。


核心概念

1. 什麼是方法覆寫?

在 Python 中,子類別可以 重新定義(override)父類別已經存在的同名方法。當我們對子類別的實例呼叫這個方法時,Python 會先在子類別的命名空間尋找,若找到就直接執行子類別的版本;若找不到,才會往上尋找父類別的實作。

重點:方法覆寫不會改變父類別本身,只是「在子類別裡提供另一套實作」。

2. 使用 super() 呼叫父類別的方法

有時候我們想在子類別的覆寫方法中,先保留父類別原有的行為,再加入額外的處理。這時可以使用 super() 取得父類別的物件,呼叫其原本的方法。

class Animal:
    def speak(self):
        print("Animal makes a sound")
class Dog(Animal):
    def speak(self):
        # 先執行父類別的行為
        super().speak()
        # 再加入子類別的行為
        print("Dog barks")

3. 覆寫與抽象方法(abstract method)

在使用抽象基底類別(abc.ABC)時,父類別會定義 抽象方法,強制子類別必須實作(override)這些方法,否則無法實例化。

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass   # 必須在子類別覆寫

4. 多層繼承的覆寫順序(MRO)

若一個類別同時繼承多個父類別,Python 會根據 Method Resolution Order (MRO) 依序搜尋方法。super() 會依照 MRO 呼叫下一個類別的相同方法,這對於 鑽石繼承(diamond inheritance)尤為重要。

class A:
    def hello(self):
        print("Hello from A")

class B(A):
    def hello(self):
        print("Hello from B")
        super().hello()

class C(A):
    def hello(self):
        print("Hello from C")
        super().hello()

class D(B, C):
    pass

d = D()
d.hello()   # 輸出順序遵循 D → B → C → A

程式碼範例

以下提供 5 個實用範例,展示方法覆寫在不同情境下的寫法與意義。

範例 1:最基本的覆寫

class Vehicle:
    def move(self):
        print("Vehicle is moving")

class Car(Vehicle):
    def move(self):
        print("Car drives on the road")
v = Vehicle()
c = Car()
v.move()   # Vehicle is moving
c.move()   # Car drives on the road

說明Car 重新定義了 move,即使 Vehicle 仍保有原本的實作。

範例 2:使用 super() 加強父類別行為

class Logger:
    def log(self, msg):
        print(f"[LOG] {msg}")

class FileLogger(Logger):
    def log(self, msg):
        super().log(msg)               # 先印出一般 LOG
        with open("app.log", "a") as f:
            f.write(msg + "\n")        # 再寫入檔案
logger = FileLogger()
logger.log("程式啟動")
# 螢幕上會顯示 [LOG] 程式啟動,且同時寫入 app.log

範例 3:抽象基底類別的必須覆寫

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def pay(self, amount):
        """執行付款動作"""

class CreditCardProcessor(PaymentProcessor):
    def pay(self, amount):
        print(f"使用信用卡付款 {amount} 元")
processor = CreditCardProcessor()
processor.pay(1200)   # 使用信用卡付款 1200 元

重點:若子類別忘記實作 pay,Python 會在建立實例時拋出 TypeError

範例 4:多層繼承與 MRO

class Base:
    def greet(self):
        print("Hello from Base")

class MixinA(Base):
    def greet(self):
        print("Hello from MixinA")
        super().greet()

class MixinB(Base):
    def greet(self):
        print("Hello from MixinB")
        super().greet()

class Concrete(MixinA, MixinB):
    pass
obj = Concrete()
obj.greet()
# 輸出:
# Hello from MixinA
# Hello from MixinB
# Hello from Base

說明Concrete 依照 MRO (Concrete → MixinA → MixinB → Base) 呼叫 greet

範例 5:覆寫 __str__ 讓物件印出易讀資訊

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 預設會印出 <Person object at 0x...>
    # 我們覆寫 __str__ 讓它更友善
    def __str__(self):
        return f"Person(name={self.name}, age={self.age})"
p = Person("小明", 28)
print(p)   # Person(name=小明, age=28)

技巧:覆寫特殊方法(如 __str____repr__)可提升除錯與日誌的可讀性。


常見陷阱與最佳實踐

陷阱 可能的結果 解決方式 / 最佳實踐
忘記呼叫 super() 子類別覆寫後,父類別的重要初始化或清理程式碼不會執行,導致資源洩漏或狀態不完整。 若需要保留父類別行為,務必在覆寫方法中加入 super().method_name(...)
覆寫時改變參數簽名 呼叫方仍以父類別的簽名傳參,會拋出 TypeError 或產生不預期的行為。 保持相同的 參數數量與名稱,或使用 *args, **kwargs 轉發。
多層繼承時忘記 MRO 子類別的 super() 可能呼叫錯誤的父類別,導致重複執行或根本不執行。 熟悉 Method Resolution Order,可使用 ClassName.__mro__ 觀察繼承順序。
抽象方法未實作 直接實例化子類別會得到 TypeError: Can't instantiate abstract class … 確認所有 @abstractmethod 都已在子類別完成覆寫。
過度覆寫 子類別的行為與父類別差異太大,失去「is‑a」關係的語意,程式碼難以理解。 遵循 Liskov 替換原則(LSP):子類別應該可以在任何需要父類別的地方安全替換使用。

最佳實踐小結

  1. 保持簽名一致:除非有特別需求,覆寫方法的參數清單應與父類別相同。
  2. 使用 super():盡可能呼叫父類別的實作,尤其是 __init____enter__/__exit__ 等資源管理相關的方法。
  3. 文件說明:在覆寫的方法上加上 docstring,說明與父類別的差異或新增的行為。
  4. 測試覆寫行為:撰寫單元測試,確保子類別在覆寫後仍符合預期功能。
  5. 遵守 LSP:子類別的行為不應違背父類別的契約,避免「違反預期」的例外。

實際應用場景

場景 為什麼需要覆寫 範例說明
Web 框架的視圖(View) 基底視圖提供通用的請求處理流程,子類別根據不同路由實作具體回應。 Django 的 View 類別,子類別覆寫 get()post() 等方法。
資料庫模型的儲存行為 基本模型只負責欄位定義,子類別在儲存前需要自訂驗證或自動填充欄位。 Django Model.save() 覆寫,加入 slugifyupdated_at
遊戲角色的行為 父類別 Character 定義共通屬性,子類別(如 WizardWarrior)覆寫 attack() 產生不同攻擊方式。 Wizard.attack() 施放魔法;Warrior.attack() 揮舞武器。
訊息日誌系統 基礎日誌只寫入標準輸出,子類別可覆寫寫入檔案、資料庫或遠端服務。 前文的 FileLogger 範例。
插件機制 主程式提供抽象介面,外部插件必須覆寫介面方法才能被正確呼叫。 PluginBase 抽象類別,第三方套件實作 execute()

總結

方法覆寫是 Python 物件導向 中不可或缺的技巧,讓我們能在保留父類別共通行為的同時,為子類別注入專屬的邏輯。掌握以下要點,就能在實務開發中有效運用:

  • 保持方法簽名一致,避免不必要的錯誤。
  • 適時使用 super(),確保父類別的初始化或清理工作不會遺漏。
  • 了解 MRO,在多重繼承環境下正確呼叫父類別方法。
  • 遵守 Liskov 替換原則,讓子類別仍能在父類別預期的情境中安全使用。
  • 撰寫文件與測試,讓覆寫的行為清晰可追蹤。

透過上述概念與實例,你已具備在日常 Python 專案中 自信地運用方法覆寫 的能力。未來不論是建構框架、設計插件系統,或是開發大型業務邏輯,都能從這個基礎出發,寫出更具彈性、可維護的程式碼。祝你在 OOP 的旅程中持續成長,寫出更優雅的 Python!