本文 AI 產出,尚未審核

Python 物件導向程式設計(OOP)── 屬性與方法

簡介

在 Python 中,物件導向程式設計(OOP) 是組織程式碼、提升可維護性與可重用性的核心概念。
屬性(Attribute)與方法(Method)是類別(class)最基本的構成單位:屬性用來保存物件的狀態,方法則描述物件能執行的行為。掌握它們的使用方式,才能寫出結構清晰、易於擴充的程式。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶領讀者建立 Python OOP 的基礎,適合剛接觸物件導向的初學者,也能為已有經驗的開發者提供實務參考。


核心概念

1. 類別與實例(Class & Instance)

  • 類別 是藍圖,定義了物件擁有的屬性與方法。
  • 實例(instance)是根據類別產生的具體物件,每個實例都有自己的屬性值。
class Car:
    """一個簡單的車子類別"""
    # 類別屬性(所有實例共享)
    wheels = 4

    def __init__(self, brand, color):
        # 實例屬性(每個實例獨有)
        self.brand = brand
        self.color = color

    def drive(self):
        """模擬開車"""
        print(f"{self.brand} ({self.color}) 正在行駛...")
# 建立兩個實例
c1 = Car("Toyota", "紅色")
c2 = Car("Honda", "藍色")

c1.drive()   # Toyota (紅色) 正在行駛...
c2.drive()   # Honda (藍色) 正在行駛...
print(Car.wheels)   # 4

2. 實例屬性 vs 類別屬性

類別屬性 實例屬性
定義在類別本身,所有實例共用 定義在 __init__ 或其他方法中,只屬於該實例
變更會影響所有實例 變更只影響該實例
常用於常數、計數器等 用於描述個別物件的狀態
class Counter:
    total = 0                     # 類別屬性:全域計數

    def __init__(self):
        Counter.total += 1       # 每產生一個實例,總數加一
        self.id = Counter.total  # 實例屬性:唯一編號

c1 = Counter()
c2 = Counter()
print(Counter.total)   # 2
print(c1.id, c2.id)    # 1 2

3. 方法的種類

3.1 實例方法(Instance Method)

最常見的方式,第一個參數固定為 self,代表呼叫此方法的實例本身。

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        """計算矩形面積"""
        return self.width * self.height

3.2 類別方法(Class Method)

使用 @classmethod 裝飾器,第一個參數為 cls,代表類別本身。常用於工廠函式或修改類別屬性。

class Person:
    population = 0

    def __init__(self, name):
        self.name = name
        Person.population += 1

    @classmethod
    def get_population(cls):
        """回傳目前人口數"""
        return cls.population

3.3 靜態方法(Static Method)

使用 @staticmethod 裝飾器,既不接受 self 也不接受 cls。適合把「與類別相關」但不需要存取類別或實例狀態的函式放在類別命名空間中。

class MathUtil:
    @staticmethod
    def is_even(n):
        """判斷是否為偶數"""
        return n % 2 == 0

4. property:封裝 getter / setter

直接存取屬性雖然簡潔,但在需要驗證、計算或延遲載入時,使用 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: 溫度不能低於絕對零度

5. 多重繼承與方法解析度順序(MRO)

Python 支援多重繼承,但若同名屬性或方法出現在多個父類別,解析度順序(Method Resolution Order) 決定哪個被呼叫。super() 可協助正確呼叫父類別的實作。

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

class B(A):
    def greet(self):
        print("Hello from B")
        super().greet()   # 呼叫 A 的 greet

class C(A):
    def greet(self):
        print("Hello from C")
        super().greet()   # 呼叫 A 的 greet

class D(B, C):
    pass

d = D()
d.greet()
# 輸出:
# Hello from B
# Hello from C
# Hello from A

常見陷阱與最佳實踐

常見問題 說明 解決方式
屬性名稱衝突 類別屬性與實例屬性同名,會導致意外覆寫。 盡量使用前綴(如 self._value)或明確區分類別屬性(ClassName.attr)。
忘記傳 self 定義實例方法時忘記第一個參數,呼叫會出錯。 讓 IDE 或 lint 工具檢查函式簽名。
過度使用 @property 每個屬性都包成 property,會降低效能且增加程式碼複雜度。 只在需要驗證或計算時使用,普通屬性直接公開即可。
濫用 @staticmethod 把與類別無關的函式塞進類別,破壞單一職責原則。 若函式獨立於類別,直接放在模組層級。
多重繼承的 MRO 混亂 不熟悉 MRO 會產生「方法不被呼叫」或「重複呼叫」的問題。 使用 class MyClass(Base1, Base2): 前先畫出繼承圖,必要時使用 super()

最佳實踐

  1. 明確區分類別屬性與實例屬性:使用大寫常數命名類別屬性(如 MAX_SPEED = 120),用小寫或 _ 開頭表示私有實例屬性。
  2. 盡量保持方法短小:每個方法只做一件事,讓單元測試更容易。
  3. 使用 @classmethod 建立工廠方法:有多種初始化方式時,比起在外部寫多個建構子,工廠方法更具可讀性。
  4. 利用 property 隱藏實作細節:對外提供簡潔介面,未來若需改變儲存方式,只要改寫 getter/setter 即可。
  5. 寫文件字串(docstring):每個類別與方法都加上說明,配合 IDE 的自動補全,提升團隊協作效率。

實際應用場景

1. 網路 API 客戶端

import requests

class GitHubUser:
    """簡易的 GitHub 使用者 API 包裝"""

    base_url = "https://api.github.com/users"

    def __init__(self, username):
        self.username = username
        self._data = None   # 延遲載入

    @property
    def data(self):
        """首次存取時才向 GitHub 請求資料"""
        if self._data is None:
            resp = requests.get(f"{self.base_url}/{self.username}")
            resp.raise_for_status()
            self._data = resp.json()
        return self._data

    def repo_count(self):
        """回傳公開倉庫數量(讀取快取)"""
        return self.data.get("public_repos", 0)

# 使用
user = GitHubUser("torvalds")
print(user.repo_count())   # 6(依實際情況而定)

透過屬性封裝 API 請求,使呼叫者只需要關注 repo_count(),而不必自行處理 HTTP、快取等細節。

2. 金融交易系統的交易單(Order)

class Order:
    """交易單模型,包含狀態變化的封裝"""

    STATUS_NEW = "new"
    STATUS_FILLED = "filled"
    STATUS_CANCELED = "canceled"

    def __init__(self, symbol, qty, price):
        self.symbol = symbol
        self.qty = qty
        self.price = price
        self._status = self.STATUS_NEW

    @property
    def status(self):
        return self._status

    def fill(self):
        if self._status != self.STATUS_NEW:
            raise RuntimeError("只能在新單狀態下成交")
        self._status = self.STATUS_FILLED

    def cancel(self):
        if self._status == self.STATUS_FILLED:
            raise RuntimeError("已成交的單無法取消")
        self._status = self.STATUS_CANCELED

將「只能在特定狀態下」的商業規則寫在方法內,避免外部直接改變 _status,提升系統的資料完整性。

3. GUI 元件的屬性綁定

import tkinter as tk

class CounterLabel(tk.Label):
    """自訂的標籤,顯示計數值並自動更新"""

    def __init__(self, master=None, **kw):
        super().__init__(master, **kw)
        self._count = 0
        self.config(text=str(self._count))

    @property
    def count(self):
        return self._count

    @count.setter
    def count(self, value):
        self._count = value
        self.config(text=str(self._count))   # UI 同步更新

# 範例使用
root = tk.Tk()
lbl = CounterLabel(root)
lbl.pack()

def inc():
    lbl.count += 1

btn = tk.Button(root, text="加一", command=inc)
btn.pack()
root.mainloop()

property 讓 UI 元件的內部狀態與畫面顯示保持一致,呼叫者只需要改變 lbl.count 即可。


總結

  • 屬性 用來保存物件的狀態,分為 類別屬性(共享)與 實例屬性(獨立)。
  • 方法 定義物件的行為,常見的有 實例方法類別方法靜態方法,各自適用不同情境。
  • @property@classmethod@staticmethod 為 Python 提供的語法糖,讓程式碼更具可讀性與可維護性。
  • 熟悉 MROsuper(),才能在多重繼承中正確呼叫父類別的實作。
  • 在實務開發中,透過屬性封裝、方法驗證與工廠模式,可以把 商業規則資料驗證外部資源存取 隱藏於類別內部,讓呼叫端只關心「我要做什麼」而不是「怎麼做」。

掌握了屬性與方法的正確使用方式,你就能在 Python 專案中建立結構清晰、易於測試與維護的物件導向程式碼。祝你寫程式愉快,持續探索 OOP 的無限可能!