本文 AI 產出,尚未審核
Python 物件導向程式設計:靜態方法與類別方法
簡介
在 Python 的 OOP(Object‑Oriented Programming)中,除了最常見的 實例方法,還有兩種特殊的方法——靜態方法(@staticmethod)與類別方法(@classmethod)。它們看起來只是語法上的小裝飾,卻在設計可重用、可維護的程式碼時扮演關鍵角色。
- 靜態方法不需要存取物件或類別本身的狀態,適合把「純函式」封裝在類別命名空間內,讓程式結構更清晰。
- 類別方法則會自動收到當前類別(
cls)作為第一個參數,常用來實作 替代建構子、工廠模式,或在子類別中保留正確的類別資訊。
了解何時使用哪一種方法,不僅能避免程式執行時的意外行為,還能提升程式的可讀性與可測試性。以下將從概念、範例、常見陷阱與實務應用逐一說明。
核心概念
1. 靜態方法(@staticmethod)
靜態方法本質上是一個普通函式,只是被放在類別裡以便於組織。
- 不會自動傳入
self或cls,因此在方法內無法直接存取實例屬性或類別屬性。 - 仍然可以透過類別或實例呼叫,語法上沒有差別。
class MathUtil:
@staticmethod
def add(a, b):
"""兩數相加,純函式,無需存取類別資訊。"""
return a + b
# 呼叫方式
result = MathUtil.add(3, 5) # 8
obj = MathUtil()
print(obj.add(10, 20)) # 30
重點:如果方法內部不需要
self或cls,就應該考慮使用@staticmethod,讓意圖更明確。
2. 類別方法(@classmethod)
類別方法會自動接收當前類別本身(cls)作為第一個參數。
- 可以存取或修改類別屬性,但仍無法直接操作實例屬性。
- 常見用途包括 替代建構子(Factory),或在子類別呼叫時自動取得正確的類別。
class Person:
species = "Homo sapiens" # 類別屬性
def __init__(self, name):
self.name = name
@classmethod
def from_full_name(cls, full_name):
"""透過全名建立 Person 物件,示範替代建構子。"""
first, last = full_name.split()
return cls(f"{first} {last}")
# 使用類別方法建立實例
p = Person.from_full_name("Ada Lovelace")
print(p.name) # Ada Lovelace
print(p.species) # Homo sapiens
3. 靜態方法 vs 類別方法:何時選擇?
| 特性 | @staticmethod |
@classmethod |
|---|---|---|
| 自動傳入參數 | 無 | cls(類別本身) |
| 能否存取類別屬性 | ❌(必須透過類別名稱手動) | ✅(直接使用 cls) |
| 主要用途 | 純函式、工具函式 | 替代建構子、工廠方法、與類別狀態相關的操作 |
| 繼承時的行為 | 仍是普通函式,呼叫時不會自動改成子類別方法 | 會自動指向子類別,支援多型 |
小提醒:即使靜態方法可以透過
ClassName.method()呼叫,也不代表它能存取ClassName的屬性;若需要存取,請改用類別方法。
4. 程式碼範例
以下提供 五個實用範例,示範靜態方法與類別方法在不同情境下的最佳寫法。
範例 1:工具函式 – 文字處理
class StringHelper:
@staticmethod
def to_snake_case(text: str) -> str:
"""將 CamelCase 文字轉成 snake_case。"""
import re
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', text)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
print(StringHelper.to_snake_case("HelloWorld")) # hello_world
範例 2:類別屬性累計 – 計算實例總數
class Widget:
_counter = 0 # 私有類別屬性
def __init__(self):
type(self)._counter += 1
@classmethod
def total_created(cls) -> int:
"""回傳已建立的 Widget 數量。"""
return cls._counter
w1, w2, w3 = Widget(), Widget(), Widget()
print(Widget.total_created()) # 3
範例 3:工廠方法 – 依參數回傳不同子類別
class Shape:
@classmethod
def create(cls, kind: str, **kwargs):
"""根據 kind 回傳對應的 Shape 子類別實例。"""
subclasses = {sub.__name__.lower(): sub for sub in cls.__subclasses__()}
if kind.lower() not in subclasses:
raise ValueError(f"未知形狀: {kind}")
return subclasses[kind.lower()](**kwargs)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
class Square(Shape):
def __init__(self, side):
self.side = side
c = Shape.create("circle", radius=5)
s = Shape.create("square", side=3)
print(type(c), c.radius) # <class '__main__.Circle'> 5
print(type(s), s.side) # <class '__main__.Square'> 3
範例 4:繼承中的類別方法 – 正確返回子類別
class BaseLogger:
prefix = "[LOG]"
@classmethod
def log(cls, message):
print(f"{cls.prefix} {message}")
class FileLogger(BaseLogger):
prefix = "[FILE]"
BaseLogger.log("系統啟動") # [LOG] 系統啟動
FileLogger.log("寫入檔案") # [FILE] 寫入檔案
範例 5:混合使用 – 透過類別方法取得資料,再用靜態方法格式化
class Temperature:
_data = {"C": 0, "F": 32}
@classmethod
def from_celsius(cls, c):
"""以攝氏度建立 Temperature 物件。"""
f = cls.c_to_f(c)
obj = cls()
obj.c = c
obj.f = f
return obj
@staticmethod
def c_to_f(c):
"""攝氏轉華氏,純數學運算,適合做靜態方法。"""
return c * 9 / 5 + 32
t = Temperature.from_celsius(25)
print(t.c, t.f) # 25 77.0
常見陷阱與最佳實踐
| 陷阱 | 說明 | 建議的做法 |
|---|---|---|
把需要 cls 的程式寫成 @staticmethod |
會導致無法存取類別屬性或子類別資訊,執行時拋出 AttributeError。 |
先確認是否真的不需要類別資訊,若需要則改用 @classmethod。 |
| 在靜態方法內直接使用類別名稱存取類別屬性 | 子類別繼承時仍會指向父類別,破壞多型。 | 若需要動態取得類別屬性,改用 @classmethod 並使用 cls。 |
忘記在類別方法中返回 cls 的實例 |
只返回父類別實例,子類別呼叫時失去多型。 | 使用 return cls(...),確保返回正確的子類別物件。 |
| 把過於複雜的邏輯寫在靜態方法裡 | 靜態方法應保持「純函式」特性,過度耦合會降低可測試性。 | 把複雜流程拆成多個小函式,或考慮使用實例方法。 |
直接在類別外部呼叫 ClassName.__init__ |
會繞過 @classmethod / @staticmethod 的封裝,破壞封裝性。 |
只透過建構子或工廠方法建立物件。 |
最佳實踐
- 意圖先行:在寫方法前,先問自己「這個方法需要存取實例狀態、類別狀態,還是兩者都不需要?」
- 保持單一職責:靜態方法適合做純粹的工具函式,類別方法適合做物件產生或類別層級的設定。
- 文件化:在每個
@staticmethod/@classmethod前寫清楚說明,尤其要說明為何不使用實例方法。 - 測試覆蓋:靜態方法與類別方法同樣需要單元測試,確保它們在子類別繼承時仍能正確工作。
實際應用場景
- 設定與常數管理:將所有與設定檔無關、僅靠參數計算的功能寫成靜態方法,讓其他模組呼叫時不必建立不必要的物件。
- 工廠模式(Factory Pattern):大型系統常會根據不同參數回傳不同子類別的實例,正如範例 3 所示,使用
@classmethod可保證子類別正確返回。 - 資料驗證與轉換:驗證規則或格式轉換往往與個別實例無關,適合寫成靜態方法,讓模型類別本身即成為「驗證器」。
- 日誌與監控:在基礎類別中提供
@classmethod的log方法,子類別只要改變prefix或handler即可自訂日誌行為,避免在每個實例中重複設定。 - 多語言或多幣別系統:使用類別方法取得當前語系/幣別設定,然後靜態方法執行實際的格式化或計算,保持程式碼的清晰層次。
總結
- 靜態方法是「封裝在類別內的純函式」,適合不需要任何類別或實例資訊的工具程式。
- 類別方法則會自動取得類別本身 (
cls),常用於替代建構子、工廠模式或類別層級的狀態管理。 - 正確選擇與使用這兩種方法,能讓程式碼的可讀性、可維護性大幅提升,同時避免在繼承與多型情境下的意外行為。
掌握了靜態方法與類別方法之間的差異與最佳實踐,你就能在日常開發中更靈活地組織程式碼,寫出既清晰又強韌的 Python 應用。祝你寫程式愉快!