本文 AI 產出,尚未審核

Python 物件導向程式設計:靜態方法與類別方法


簡介

在 Python 的 OOP(Object‑Oriented Programming)中,除了最常見的 實例方法,還有兩種特殊的方法——靜態方法(@staticmethod類別方法(@classmethod。它們看起來只是語法上的小裝飾,卻在設計可重用、可維護的程式碼時扮演關鍵角色。

  • 靜態方法不需要存取物件或類別本身的狀態,適合把「純函式」封裝在類別命名空間內,讓程式結構更清晰。
  • 類別方法則會自動收到當前類別(cls)作為第一個參數,常用來實作 替代建構子工廠模式,或在子類別中保留正確的類別資訊。

了解何時使用哪一種方法,不僅能避免程式執行時的意外行為,還能提升程式的可讀性與可測試性。以下將從概念、範例、常見陷阱與實務應用逐一說明。


核心概念

1. 靜態方法(@staticmethod

靜態方法本質上是一個普通函式,只是被放在類別裡以便於組織。

  • 不會自動傳入 selfcls,因此在方法內無法直接存取實例屬性或類別屬性。
  • 仍然可以透過類別或實例呼叫,語法上沒有差別。
class MathUtil:
    @staticmethod
    def add(a, b):
        """兩數相加,純函式,無需存取類別資訊。"""
        return a + b

# 呼叫方式
result = MathUtil.add(3, 5)        # 8
obj = MathUtil()
print(obj.add(10, 20))            # 30

重點:如果方法內部不需要 selfcls,就應該考慮使用 @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 的封裝,破壞封裝性。 只透過建構子或工廠方法建立物件。

最佳實踐

  1. 意圖先行:在寫方法前,先問自己「這個方法需要存取實例狀態、類別狀態,還是兩者都不需要?」
  2. 保持單一職責:靜態方法適合做純粹的工具函式,類別方法適合做物件產生類別層級的設定
  3. 文件化:在每個 @staticmethod / @classmethod 前寫清楚說明,尤其要說明為何不使用實例方法。
  4. 測試覆蓋:靜態方法與類別方法同樣需要單元測試,確保它們在子類別繼承時仍能正確工作。

實際應用場景

  1. 設定與常數管理:將所有與設定檔無關、僅靠參數計算的功能寫成靜態方法,讓其他模組呼叫時不必建立不必要的物件。
  2. 工廠模式(Factory Pattern):大型系統常會根據不同參數回傳不同子類別的實例,正如範例 3 所示,使用 @classmethod 可保證子類別正確返回。
  3. 資料驗證與轉換:驗證規則或格式轉換往往與個別實例無關,適合寫成靜態方法,讓模型類別本身即成為「驗證器」。
  4. 日誌與監控:在基礎類別中提供 @classmethodlog 方法,子類別只要改變 prefixhandler 即可自訂日誌行為,避免在每個實例中重複設定。
  5. 多語言或多幣別系統:使用類別方法取得當前語系/幣別設定,然後靜態方法執行實際的格式化或計算,保持程式碼的清晰層次。

總結

  • 靜態方法是「封裝在類別內的純函式」,適合不需要任何類別或實例資訊的工具程式。
  • 類別方法則會自動取得類別本身 (cls),常用於替代建構子工廠模式類別層級的狀態管理
  • 正確選擇與使用這兩種方法,能讓程式碼的可讀性、可維護性大幅提升,同時避免在繼承與多型情境下的意外行為。

掌握了靜態方法與類別方法之間的差異與最佳實踐,你就能在日常開發中更靈活地組織程式碼,寫出既清晰強韌的 Python 應用。祝你寫程式愉快!