本文 AI 產出,尚未審核

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

單元:繼承(單繼承、多繼承)


簡介

在物件導向程式設計(OOP)中,繼承是最核心的概念之一。透過繼承,我們可以讓一個類別(子類別)直接取得另一個類別(父類別)已實作好的屬性與方法,從而減少重複程式碼、提升程式的可維護性與可擴充性。

在實務開發裡,無論是設計 GUI 元件、建立資料模型,或是開發框架與套件,都會頻繁運用單繼承多繼承的技巧。掌握正確的繼承寫法,不僅能讓程式結構更清晰,也能避免因繼承層級過深或衝突而產生的難以除錯的問題。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶領讀者深入了解 Python 中的繼承機制,並提供可直接套用於日常開發的範例程式碼。


核心概念

1. 單繼承(Single Inheritance)

單繼承指的是子類別只繼承自 一個 父類別。這是最常見、最直觀的繼承方式。

class Animal:
    """所有動物的共通屬性與行為"""
    def __init__(self, name: str):
        self.name = name

    def speak(self) -> str:
        return "..."

class Dog(Animal):
    """Dog 繼承自 Animal,並覆寫 speak 方法"""
    def speak(self) -> str:
        return "汪汪!"

# 使用範例
my_dog = Dog("小黑")
print(my_dog.name)   # -> 小黑
print(my_dog.speak())# -> 汪汪!
  • 重點:子類別自動取得 Animal.__init__ 以及其他未被覆寫的方法。
  • super():在子類別想要呼叫父類別的 __init__(或其他方法)時,使用 super() 可以避免硬編碼父類別名稱,提升未來維護彈性。
class Cat(Animal):
    def __init__(self, name: str, lives: int = 9):
        # 呼叫父類別的 __init__ 以設定 name
        super().__init__(name)
        self.lives = lives

    def speak(self) -> str:
        return "喵喵!"

my_cat = Cat("咪咪")
print(my_cat.name, my_cat.lives)   # -> 咪咪 9

2. 多繼承(Multiple Inheritance)

Python 允許一個類別同時繼承自 多個 父類別。多繼承的威力在於可以把多個「功能」混合到同一個類別中,但也必須小心 方法解析順序(MRO) 的衝突問題。

class Flyer:
    """提供飛行能力的 mixin"""
    def fly(self) -> str:
        return "我會飛!"

class Swimmer:
    """提供游泳能力的 mixin"""
    def swim(self) -> str:
        return "我會游泳!"

class Duck(Flyer, Swimmer):
    """鴨子同時具備飛行與游泳能力"""
    pass

daffy = Duck()
print(daffy.fly())   # -> 我會飛!
print(daffy.swim())  # -> 我會游泳!

2.1 方法解析順序(MRO)

當多個父類別中有同名方法時,Python 會依照 C3 線性化 演算法決定呼叫哪一個實作。可以透過 ClassName.__mro__help(ClassName) 觀察順序。

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

class B(A):
    def hello(self):
        return "Hello from B"

class C(A):
    def hello(self):
        return "Hello from C"

class D(B, C):
    pass

print(D.__mro__)  # (<class '__main__.D'>, <class '__main__.B'>,
                  #  <class '__main__.C'>, <class '__main__.A'>,
                  #  <class 'object'>)

d = D()
print(d.hello())  # -> Hello from B   (先找 B,再找 C,最後才是 A)

小技巧:在多繼承中使用 super() 時,一定要 讓父類別的 __init__(或其他方法)也使用 super(),才能形成完整的 MRO 鏈,避免某個父類別的初始化被跳過。

class LoggerMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)   # 交給下一層處理
        self.log = []

    def record(self, msg: str):
        self.log.append(msg)

class TimestampMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.created_at = __import__('datetime').datetime.now()

class DataStore(LoggerMixin, TimestampMixin):
    def __init__(self, data):
        super().__init__()          # 依照 MRO 呼叫兩個 mixin 的 __init__
        self.data = data

store = DataStore([1, 2, 3])
print(store.created_at)   # 顯示建立時間
print(store.log)          # []
store.record("新增資料")
print(store.log)          # ['新增資料']

3. isinstanceissubclass

在使用繼承時,我們常會需要檢查物件或類別的關係。

print(isinstance(my_dog, Animal))   # True
print(isinstance(my_dog, Dog))      # True
print(issubclass(Dog, Animal))      # True
print(issubclass(Duck, Flyer))      # True

4. 抽象基底類別(ABC)與介面概念

雖然 Python 沒有正式的「介面」概念,但可以透過 abc 模組建立抽象方法,強制子類別實作。

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        """回傳圖形面積"""

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        import math
        return math.pi * self.radius ** 2

c = Circle(3)
print(c.area())   # 28.274333882308138

常見陷阱與最佳實踐

陷阱 說明 解決方案
忘記呼叫 super().__init__() 子類別自行實作 __init__ 卻沒有呼叫 super(),導致父類別的屬性未被初始化。 在每個子類別的 __init__一定 使用 super().__init__(*args, **kwargs)
多繼承的命名衝突 不同父類別提供同名方法,容易產生意外的行為。 使用 Mixin(只提供單一功能)並確保方法名稱具體化;或在子類別中明確覆寫並呼叫所需的父類別方法。
MRO 不符合預期 由於 C3 線性化規則,某些繼承順序會產生不直觀的解析順序。 使用 ClassName.__mro__ 檢查;必要時調整繼承順序或重構為組合(Composition)而非多繼承。
過度使用多繼承 多層、多父的結構會讓程式碼難以閱讀、除錯。 儘量採用 單繼承 + Mixin 的方式,或改用 組合(將功能物件作為屬性注入)。
抽象類別忘記實作抽象方法 直接實例化抽象類別會拋出 TypeError 在子類別實作所有 @abstractmethod,或使用 ABCMeta 檢查。

最佳實踐

  1. 使用 super():不論單繼承或多繼承,都應該使用 super() 來呼叫父類別,讓 MRO 正常運作。
  2. Mixin 命名慣例:以 Mixin 為結尾(如 LoggerMixin)讓閱讀者一眼辨識它是功能混入而非完整類別。
  3. 保持繼承樹淺層:盡量讓類別層級不超過 2~3 層,過深的繼承會增加耦合度。
  4. 文件化每個類別的責任:在類別說明字串中寫明「此類別負責什麼」與「它繼承了哪些行為」。
  5. 測試 MRO:在單元測試中加入 assert Class.__mro__ == expected_tuple,確保未因重構而改變解析順序。

實際應用場景

1. Web 框架的視圖(View)與混入(Mixin)

許多 Python Web 框架(如 Django、Flask)會提供 LoginRequiredMixinPermissionRequiredMixin 等,用於在 類別型視圖 中快速加入授權檢查。

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import View

class DashboardView(LoginRequiredMixin, View):
    """僅限已登入使用者能存取的儀表板"""
    def get(self, request):
        return HttpResponse("歡迎來到儀表板!")

2. GUI 元件的多功能組合

在桌面應用程式(如 PyQt、Tkinter)中,常需要把「可拖曳」與「可縮放」的行為分別寫成 Mixin,然後組合成最終元件。

class DraggableMixin:
    def mousePressEvent(self, event):
        self._drag_start = event.pos()

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            self.move(self.pos() + event.pos() - self._drag_start)

class ResizableMixin:
    def resizeEvent(self, event):
        # 依需求調整子元件大小
        super().resizeEvent(event)   # 保持原有行為

class MyWindow(QWidget, DraggableMixin, ResizableMixin):
    pass

3. 資料模型的領域驅動設計(DDD)

在大型系統中,領域模型 常使用抽象基底類別(如 EntityValueObject)提供共通的 ID、相等性比較,子類別只負責自己的業務邏輯。

class Entity(ABC):
    @property
    @abstractmethod
    def id(self):
        ...

class User(Entity):
    def __init__(self, user_id: int, name: str):
        self._id = user_id
        self.name = name

    @property
    def id(self):
        return self._id

總結

  • 繼承是 OOP 的基礎,讓我們可以在 單繼承 中重用父類別的行為,也能在 多繼承 中混合多個功能。
  • 正確使用 super()、了解 MRO、以及善用 Mixin抽象基底類別,是避免常見錯誤的關鍵。
  • 在實務開發中,繼承常見於 Web 框架的視圖、GUI 元件的功能組合、以及領域模型的抽象層。
  • 最重要的是:保持繼承樹簡潔、以組合取代過度的多繼承,才能寫出易於維護、可測試且具彈性的程式碼。

透過本篇的概念說明與實作範例,你已具備在 Python 專案中安全、有效地運用繼承的能力。接下來不妨把這些技巧套用到自己的程式碼庫,觀察可讀性與維護成本是否真的提升!祝開發愉快 🚀