本文 AI 產出,尚未審核

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

主題:類別與物件


簡介

在程式設計的世界裡,物件導向程式設計(OOP) 是最常被使用的範式之一。它讓我們可以把「資料」與「行為」封裝在同一個概念——類別(class) 之中,進而產生物件(object)。對於 Python 初學者而言,掌握類別與物件的概念不僅能寫出更易讀、可維護的程式碼,也為日後開發大型系統、使用框架(如 Django、Flask)奠定基礎。

本篇文章將從 什麼是類別與物件如何定義與使用常見陷阱與最佳實踐,以及 實務應用場景 四個面向,逐步帶領讀者建立扎實的 OOP 基礎,並以多個實用範例說明每個概念的實作方式。


核心概念

1. 類別(Class)與物件(Instance)

  • 類別:是一個藍圖(blueprint),描述一組具有相同屬性(attributes)與方法(methods)的物件。
  • 物件:是根據類別藍圖所產生的實體(instance),每個物件都有自己的屬性值。

類別 ≈ 料理食譜,物件 ≈ 依照食譜烹飪出的料理。

範例 1:最簡單的類別與物件

class Dog:
    """簡單的狗狗類別"""
    def __init__(self, name, age):
        self.name = name   # 屬性:名字
        self.age = age     # 屬性:年齡

    def bark(self):
        """狗叫的方法"""
        print(f"{self.name} says: Woof!")

# 建立物件(實例)
my_dog = Dog("Buddy", 3)
my_dog.bark()          # 輸出:Buddy says: Woof!
  • __init__ 是類別的建構子(constructor),在物件建立時自動被呼叫,用來初始化屬性。
  • self 代表「這個物件本身」,是所有方法的第一個參數,不可省略

2. 屬性(Attributes)與方法(Methods)

  • 屬性:用來儲存物件的狀態,可分為「實例屬性」與「類別屬性」。
  • 方法:是定義在類別內的函式,能夠操作或回傳屬性。

範例 2:類別屬性與實例屬性的差異

class Circle:
    pi = 3.14159                # 類別屬性:所有 Circle 共享

    def __init__(self, radius):
        self.radius = radius    # 實例屬性:每個 Circle 各自的半徑

    def area(self):
        """計算圓面積"""
        return Circle.pi * self.radius ** 2

c1 = Circle(2)
c2 = Circle(5)

print(c1.area())   # 12.56636
print(c2.area())   # 78.53975
print(Circle.pi)   # 3.14159
  • pi 為類別屬性,所有 Circle 物件共用同一個值。
  • 若要修改類別屬性,只能透過 Circle.pi = 新值,而不是 c1.pi = 新值(那樣會變成實例屬性)。

3. 繼承(Inheritance)與多型(Polymorphism)

  • 繼承:子類別(subclass)可以直接使用父類別(superclass)的屬性與方法,並可自行擴充或覆寫(override)。
  • 多型:不同類別的物件,只要實作相同的方法,就能在相同的介面下使用,提升程式彈性。

範例 3:簡易的繼承與方法覆寫

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("子類別必須實作 speak 方法")

class Cat(Animal):
    def speak(self):
        return f"{self.name} says: Meow"

class Dog(Animal):
    def speak(self):
        return f"{self.name} says: Woof"

# 多型示範
animals = [Cat("Mimi"), Dog("Rex")]
for a in animals:
    print(a.speak())
# 輸出:
# Mimi says: Meow
# Rex says: Woof
  • Animal 定義了一個抽象方法 speak,子類別必須自行實作,這樣在迴圈中即可同一介面呼叫不同的行為。

4. 特殊方法(Magic / Dunder Methods)

Python 為了讓類別更像內建型別,提供了許多以雙底線 __ 包圍的特殊方法(如 __str____len____add__),稱為 Dunder 方法

範例 4:自訂 __str____repr__

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f"《{self.title}》 by {self.author}"

    def __repr__(self):
        return f"Book(title={self.title!r}, author={self.author!r})"

b = Book("Python 程式設計入門", "王小明")
print(str(b))   # 《Python 程式設計入門》 by 王小明
print(repr(b))  # Book(title='Python 程式設計入門', author='王小明')
  • __str__ 用於 print() 時的「可讀性」顯示;__repr__ 用於開發者在交互式環境(REPL)檢視物件時的「正式」表示。

5. 屬性存取控制:@property 與 setter

Python 並不像 Java 那樣有明確的 privatepublic 修飾子,常用 property 來實作屬性的「讀取」與「寫入」控制。

範例 5:使用 @property 建立受保護的屬性

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius   # 前置底線表示「受保護」屬性

    @property
    def celsius(self):
        """取得攝氏溫度"""
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("溫度不可低於絕對零度")
        self._celsius = value

    @property
    def fahrenheit(self):
        """自動轉換為華氏溫度(唯讀)"""
        return self._celsius * 9 / 5 + 32

t = Temperature(25)
print(t.celsius)      # 25
print(t.fahrenheit)  # 77.0
t.celsius = -300      # 會拋出 ValueError
  • 透過 @property,可以在不改變外部呼叫方式的前提下,加入驗證或計算邏輯。

常見陷阱與最佳實踐

常見陷阱 說明 最佳做法
忘記 self 在方法定義或呼叫屬性時漏寫 self,會導致 NameError 或屬性未正確設定。 永遠在每個實例方法的第一個參數寫 self,IDE 常會提示。
類別屬性與實例屬性混用 不小心在 __init__ 之外直接寫 self.attr = ...,會把類別屬性變成實例屬性,造成共享行為失效。 明確區分:類別屬性寫在 class 區塊最上方;實例屬性只在 __init__ 或其他實例方法內使用 self.
過度使用繼承 為了「重用程式碼」而建立過深的繼承樹,導致維護困難。 優先考慮 組合(composition):在類別內持有其他類別的實例,而非直接繼承。
忽略 __repr__ 只寫 __str__,在除錯或 REPL 中看到 <Book object at 0x...>,不易辨識。 為自訂類別同時實作 __repr__,提供開發者友好的資訊。
直接存取「受保護」屬性 直接操作 _attr 破壞封裝,未來若加入驗證會造成相容性問題。 使用 @property 包裝屬性,外部只透過 getter / setter 存取。

其他實務建議

  1. 文件化:在類別與方法上加上 docstring("""說明"""),讓 IDE 能自動顯示說明。
  2. 單元測試:使用 unittestpytest 為每個類別的行為寫測試,確保繼承與多型不會意外破壞功能。
  3. 遵守 PEP 8:類別名稱使用 PascalCase,方法與屬性使用 snake_case
  4. 避免過度暴露內部細節:只把必要的屬性與方法設為公開(不加底線),其餘以 _ 開頭或使用 property 隱蔽。

實際應用場景

1. 網站後端的資料模型(例如 Django ORM)

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published_at = models.DateTimeField(null=True, blank=True)

    def publish(self):
        """將文章標記為已發布"""
        self.published_at = timezone.now()
        self.save()
  • Article 類別即是 模型(model),它繼承自 Django 的 models.Model,自動擁有 CRUD(建立、讀取、更新、刪除)功能。

2. 金融系統的交易物件

class Transaction:
    def __init__(self, amount, currency):
        self.amount = amount
        self.currency = currency

    def __add__(self, other):
        if self.currency != other.currency:
            raise ValueError("幣別不一致,無法直接相加")
        return Transaction(self.amount + other.amount, self.currency)

t1 = Transaction(100, "USD")
t2 = Transaction(250, "USD")
t3 = t1 + t2   # t3.amount 為 350
  • 透過 __add__,讓 Transaction 物件支援 + 運算子,提升程式可讀性與安全性。

3. 圖形使用者介面(GUI)元件的抽象層

import tkinter as tk

class BaseButton(tk.Button):
    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        self.configure(font=("Helvetica", 12), bg="#4CAF50", fg="white")

class ConfirmButton(BaseButton):
    def __init__(self, master, command):
        super().__init__(master, text="確認", command=command)

def on_confirm():
    print("使用者按下確認!")

root = tk.Tk()
btn = ConfirmButton(root, command=on_confirm)
btn.pack(padx=20, pady=20)
root.mainloop()
  • BaseButton 把共通的樣式封裝起來,子類別 ConfirmButton 只需要關注文字與功能,降低 UI 重複程式碼

總結

  • 類別是 OOP 的核心,提供藍圖讓我們能夠產生物件,每個物件都有自己的屬性與方法。
  • 透過 __init__self、類別屬性與實例屬性,我們可以清楚地管理資料與行為。
  • 繼承多型 讓程式碼更具彈性與可擴充性;特殊方法(Dunder)則讓自訂類別像內建型別一樣自然使用。
  • 使用 @property 來保護屬性、實作 __str__ / __repr__ 以提升除錯體驗,並遵守 PEP 8、撰寫 docstring、加入 單元測試,都是提升程式品質的好習慣。
  • 在實務上,從 Web 框架的模型金融交易的運算子重載、到 GUI 元件的抽象,皆能看到類別與物件的身影。

掌握了 類別與物件,你就能在 Python 中建立結構良好、易於維護且可擴充的程式系統。未來不論是開發 API、資料分析工具,或是大型企業級應用,OOP 都是不可或缺的基礎。祝你在 Python 的學習旅程中,玩得開心、寫得順手!