在處理 Python 類別時,您會遇到名為 @classmethod 的裝飾器。這與 @staticmethod 或一般實例方法(Instance Method)有明確的區別,擁有獨特的目的。

在這篇文章中,我們將清楚了解 @classmethod 是什麼,以及在何種情況下如何使用它,並與 @staticmethod 和實例方法進行比較。


1. 三種方法類型:實例方法 vs. 類方法 vs. 靜態方法



Python 類別基本上有三種方法類型。它們最大的不同在於 方法接收的第一個參數是什麼

class MyClass:
    # 1. 實例方法 (Instance Method)
    def instance_method(self, arg1):
        # 'self' 代表類別的實例。
        # 可以訪問和修改實例屬性(self.x)。
        print(f"實例方法被調用,參數為 {self} 和 {arg1}")

    # 2. 類方法 (Class Method)
    @classmethod
    def class_method(cls, arg1):
        # 'cls' 代表類別本身(MyClass)。
        # 主要用於訪問類屬性或作為創建實例的“工廠”。
        print(f"類方法被調用,參數為 {cls} 和 {arg1}")

    # 3. 靜態方法 (Static Method)
    @staticmethod
    def static_method(arg1):
        # 不接受 'self' 或 'cls'。
        # 是與類或實例狀態無關的輔助函數。
        # 此函數只是在類內部“附屬”。
        print(f"靜態方法被調用,參數為 {arg1}")

# 方法調用示例
obj = MyClass()

# 實例方法(通過實例調用)
obj.instance_method("Hello") 

# 類方法(可以通過類別或實例調用)
MyClass.class_method("World")
obj.class_method("World") # 這樣也可以,但通過類別調用更為明確。

# 靜態方法(可以通過類別或實例調用)
MyClass.static_method("Python")
obj.static_method("Python")

一目瞭然的比較

類別 第一個參數 訪問對象 主要用途
實例方法 self(實例) 實例屬性 (self.name) 操作或訪問對象的狀態
類方法 cls(類別) 類屬性 (cls.count) 替代建構子(工廠)
靜態方法 與類邏輯相關的輔助函數

2. @classmethod 的核心應用:工廠方法

使用 @classmethod 的最重要且普遍的案例是定義 “替代建構子(Alternative Constructor)”“工廠方法(Factory Method)”

基本構造函數 __init__ 通常接受明確的參數以初始化對象。然而,有時需要以不同的方式來創建對象(例如:字串解析、字典、特定計算值)。

使用 @classmethod,可以創建除了 __init__ 之外的另一個返回 類別實例 的“進入點”。

classmethod概念圖

示例:根據日期字串創建 Person 對象

假設 Person 類別接受 nameage。若要根據 '名字-出生年份' 格式的字串來創建對象,該怎麼做呢?

import datetime

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

    def greet(self):
        print(f"你好,我是 {self.name},今年 {self.age} 歲。")

    @classmethod
    def from_birth_year(cls, name, birth_year):
        """
        根據出生年份計算年齡並創建 Person 實例。
        """
        current_year = datetime.datetime.now().year
        age = current_year - birth_year

        # 'cls' 指的是 Person 類別本身。
        # return Person(name, age) 是等價的,但使用 'cls' 在繼承時會有優勢(詳見下文)。
        return cls(name, age)

# 使用基本構造函數
person1 = Person("Alice", 30)
person1.greet()

# 使用 @classmethod 創建的替代建構子
person2 = Person.from_birth_year("MadHatter", 1995)
person2.greet()

輸出結果:

你好,我是 Alice,今年 30 歲。
你好,我是 MadHatter,今年 30 歲。(根據當前年份 2025)

在上面的示例中,from_birth_year 方法通過 cls 參數訪問 Person 類別本身。然後利用計算得出的 age 來調用 cls(name, age),這等同於調用 Person(name, age) 並返回一個新的 Person 實例。


3. @classmethod 與繼承



“直接調用 Person(...) 不就夠了,為什麼還要使用 cls(...)?”

@classmethod 的真正威力在於 繼承cls 代表了當前方法被調用的 _那個類別_。

假設我們創建一個繼承 PersonStudent 類別。

class Student(Person):
    def __init__(self, name, age, major):
        super().__init__(name, age) # 調用父類的 __init__ 
        self.major = major

    def greet(self):
        # 方法重寫
        print(f"你好,我是 {self.major} 主修的 {self.name},今年 {self.age} 歲。")

# 在 Student 類別中調用父類的 @classmethod。
student1 = Student.from_birth_year("Jhon", 2005, "計算機科學") 
# 唉,發生 TypeError!

# 當調用 Student.from_birth_year 時,
# 會執行 Person.from_birth_year,並且這時 'cls' 會是 'Student'。
# 亦即,return cls(name, age) 會成為 return Student(name, age)。
# 但 Student 的 __init__ 需要額外的 'major' 參數。

上述代碼由於 Student__init__ 需要 major,因此會引發錯誤。但關鍵點在於 cls 指向的不是 Person 而是 Student

如果 from_birth_year@staticmethod 或在內部硬編碼為 Person(name, age),那麼調用 Student.from_birth_year 將會返回 Person 對象,而不是 Student 對象。

@classmethod 通過 cls 確保即使子類調用,也能生成正確的子類實例。(當然,如上述示例,若子類的 __init__ 簽名變更,也必須覆寫 from_birth_year。)


4. 總結

簡而言之,@classmethod 可以總結為如下幾點:

  1. @classmethod 接受 cls(類別本身)作為第一參數。

  2. 主要用途是“替代建構子(工廠方法)”。 在需要提供創建對象的多種方法時使用。

  3. 在與繼承一起使用時特別強大。 在子類中調用時,cls 會指向正確的子類,從而創建該類別的實例。

記住 @staticmethod 用於完全不需要類/實例狀態的輔助函數,@classmethod 用於需要訪問類本身的工廠方法,普通實例方法則用於處理對象的狀態 (self),這樣就可以更清晰且符合 Python 風格地編寫代碼。