HOWTO · Python

Python 中的類方法

本教程討論 Python 中的類方法。

本頁內容

類別是物件的藍圖,但也能保存它所建立的每個物件共用的資料與行為。Python 提供例項方法、類方法與靜態方法,讓程式碼能清楚表達操作需要處理的位置。

例項方法的第一個引數會接收一個例項,慣例上命名為 self。當操作需要某個特定物件的資料時,例如員工的姓名或年齡,就應使用例項方法。類方法的第一個引數則接收類別物件,慣例上命名為 cls。它可以直接透過類別呼叫,不必先建立例項,也能讀取或更新類別層級的狀態。

傳給 cls 的值是實際用來呼叫方法的類別,而不是任意例項。這項特性使類方法特別適合用於替代建構函式:方法可以驗證輸入並回傳 cls(...),讓子類別取得自身的例項。靜態方法不會接收 self 或 cls;它是恰好放在類別中的具名工具函式。

Python 提供兩種受支援的方式建立類方法:使用 classmethod() 轉換既有函式,或使用 @classmethod 裝飾器。對於新程式碼,裝飾器通常更清楚,因為方法的繫結方式就在定義處可見。轉換形式仍適合維護已經有一般函式的程式碼。

在 Python 中使用 classmethod() 函式建立類方法

classmethod() 函式接受一個函式並回傳類方法描述器。先在類別中定義函式,再將轉換後的描述器指派回該屬性。在此範例中,當以 Emp.department_name() 呼叫時,department_name 的 cls 會收到 Emp,因此能取得共用的 department 屬性。

class Emp:
    department = "Python"

    def department_name(cls):
        print(f"Department: {cls.department}")


Emp.department_name = classmethod(Emp.department_name)
Emp.department_name()

輸出:

Department: Python

這種轉換不表示 classmethod() 已經過時;它是調整既有方法的有效方式。然而,對於新定義,以下的裝飾器形式更容易瀏覽,也不會將函式定義與轉換分開。不論使用哪一種形式,當方法的用途是類別層級的設定、建構或其他不依賴單一物件的行為時,請透過類別呼叫它。

在 Python 中使用 @classmethod 裝飾器建立類方法

將 @classmethod 直接放在方法定義上方,即可將其第一個參數繫結至類別。參數名稱只是慣例而非語言規定,但 cls 能清楚表達方法的角色。方法可使用 cls 存取類別屬性、呼叫其他類方法,或建立新物件,而不必硬編碼基底類別的名稱。

以下以慣用的裝飾器形式完成同樣的類別層級查詢:

class Emp:
    department = "Python"

    @classmethod
    def department_name(cls):
        print(f"Department: {cls.department}")


Emp.department_name()

輸出:

Department: Python

類方法能被繼承。關鍵在於,即使方法定義於父類別,像 Manager.from_record(...) 這樣的呼叫仍會將 cls 繫結至 Manager。因此工廠方法應回傳 cls(...),而不是 Employee(...):後者總是會建立父類別型別。Python 不會依引數型別選擇方法來實作同名方法多載;當這些方式更能描述 API 時,請使用不同的方法名稱、選用引數,或像具名替代建構函式這樣的類方法。

下列工廠方法會解析簡單的紀錄、將年齡轉為整數,並回傳呼叫它的類別。這是成功案例,因為輸入恰好含有兩個非空白、以逗號分隔的欄位。

class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_record(cls, record):
        name, age_text = record.split(",")
        return cls(name, int(age_text))


employee = Employee.from_record("Matt,15")
print(f"{employee.name}: {employee.age}")

輸出:

Matt: 15

此呼叫中 from_record() 會回傳 Employee 物件,因為 cls 是 Employee。子類別可重複使用此方法而無須重寫解析邏輯。例如,先定義 class Manager(Employee): pass,再呼叫 Manager.from_record("Ava,30"),就會建立 Manager,因為此時 cls 指向 Manager。

輸入驗證應放在外部文字轉為結構化資料的邊界。split(",") 可能產生錯誤數量的欄位,int() 也可能拒絕非數字年齡。請提出清楚的 ValueError,讓呼叫端能刻意處理格式錯誤的紀錄,而不是得到狀態不可靠的物件。以下邊界案例會捕捉該錯誤並印出可預測的訊息。

class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_record(cls, record):
        parts = record.split(",")
        if len(parts) != 2 or not all(part.strip() for part in parts):
            raise ValueError("record must contain a name and age")
        name, age_text = parts
        try:
            return cls(name.strip(), int(age_text))
        except ValueError as error:
            raise ValueError("age must be an integer") from error


# boundary case: a record without an age is rejected
try:
    Employee.from_record("Matt,")
except ValueError as error:
    print(f"Invalid record: {error}")

輸出:

Invalid record: record must contain a name and age

當行為需要某個物件的狀態時,例如更新 self.age,請選擇例項方法。當行為需要類別共用的狀態,或必須建立接收呼叫的類別時,請選擇類方法。當邏輯在概念上屬於類別、卻不需要例項或類別狀態時,請選擇靜態方法。靜態方法仍可透過類別呼叫,但因為 Python 不會傳入 cls 引數,它們無法自動檢視或修改類別屬性。這項區分能讓 API 更直接,並讓讀者與維護者看見每個方法所依賴的項目。