本文 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 那樣有明確的 private、public 修飾子,常用 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 存取。 |
其他實務建議
- 文件化:在類別與方法上加上 docstring(
"""說明"""),讓 IDE 能自動顯示說明。 - 單元測試:使用
unittest或pytest為每個類別的行為寫測試,確保繼承與多型不會意外破壞功能。 - 遵守 PEP 8:類別名稱使用 PascalCase,方法與屬性使用 snake_case。
- 避免過度暴露內部細節:只把必要的屬性與方法設為公開(不加底線),其餘以
_開頭或使用 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 的學習旅程中,玩得開心、寫得順手!