核心概念
@classmethod 是一个装饰器(Decorator),用于将一个方法声明为“类方法”,这意味着这个方法与类本身相关联,而不是与类的某个特定实例(对象)相关联。

类方法的第一个参数不是实例(self),而是类本身(通常命名为 cls)。
语法
class MyClass:
@classmethod
def my_class_method(cls, arg1, arg2, ...):
# 方法体
# 'cls' 代表 MyClass 这个类
pass
@classmethod: 装饰器,告诉 Python 这是一个类方法。cls: 这是约定俗成的名称,代表类本身,你可以用其他名字(如my_class),但使用cls是 Python 社区的标准,能提高代码可读性。arg1, arg2, ...: 除了cls之外,方法可以接受其他参数。
@classmethod vs. @staticmethod vs. 实例方法
为了更好地理解 @classmethod,我们通常会将它与另外两种方法进行比较:实例方法和静态方法。
| 特性 | 实例方法 | 类方法 | 静态方法 |
|---|---|---|---|
| 定义方式 | def method(self, ...) |
@classmethod def method(cls, ...) |
@staticmethod def method(...) |
| 第一个参数 | self (类的实例) |
cls (类本身) |
无 (不接收 self 或 cls) |
| 如何调用 | instance.method() 或 Class.method() |
Class.method() 或 instance.method() |
Class.method() 或 instance.method() |
| 主要用途 | 操作或修改实例状态(实例的属性) | 操作或修改类状态(类的属性),或作为工厂方法 | 与类或实例无关的工具函数,逻辑上属于类的一部分 |
| 访问权限 | 可以访问实例属性 (self.attr) 和类属性 (self.Class.attr 或 cls.attr) |
可以访问类属性 (cls.attr),不能直接访问实例属性 |
不能访问实例属性或类属性(除非显式传入) |
@classmethod 的主要用途
访问和修改类状态
类属性是所有实例共享的,如果你需要修改这个共享的属性,使用类方法是非常好的方式。
class Student:
# 这是一个类属性,所有 Student 实例共享
school_name = "Harvard University"
def __init__(self, name, grade):
self.name = name
self.grade = grade
@classmethod
def change_school_name(cls, new_name):
"""修改学校名称"""
print(f"Changing school name from '{cls.school_name}' to '{new_name}'")
cls.school_name = new_name
# --- 使用 ---
student1 = Student("Alice", 95)
student2 = Student("Bob", 88)
print(f"Original school name: {Student.school_name}")
# 输出: Original school name: Harvard University
# 通过类方法修改
Student.change_school_name("Yale University")
print(f"Student 1's school: {student1.school_name}")
print(f"Student 2's school: {student2.school_name}")
# 输出:
# Student 1's school: Yale University
# Student 2's school: Yale University
# 也可以通过实例调用,但 'cls' 仍然是类本身
student1.change_school_name("MIT")
print(f"Final school name: {Student.school_name}")
# 输出: Final school name: MIT
作为“工厂方法”(Factory Method)
这是 @classmethod 一个非常强大和优雅的用途,工厂方法用于创建并返回类的实例,特别是当创建过程比较复杂,或者需要根据不同参数返回不同子类实例时。

场景: 我们有一个 Person 类,但我们希望根据输入的字符串(如 "CN" 或 "US")来创建不同格式的对象。
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
def __repr__(self):
return f"Person(first_name='{self.first_name}', last_name='{self.last_name}')"
@classmethod
def from_string(cls, person_str):
"""
这是一个工厂方法。
它接收一个字符串,解析它,并返回一个新的 Person 实例。
"""
first_name, last_name = person_str.split(" ")
# 使用 'cls()' 来创建类的实例,而不是硬编码 Person()
# 这样即使子类继承了这个方法,也能正确创建子类实例
return cls(first_name, last_name)
# --- 使用 ---
# 通常的创建方式
p1 = Person("John", "Doe")
print(p1)
# 输出: Person(first_name='John', last_name='Doe')
# 使用工厂方法创建
person_data = "Jane Smith"
p2 = Person.from_string(person_data)
print(p2)
# 输出: Person(first_name='Jane', last_name='Smith')
工厂方法的优势:
- 封装:将复杂的实例化逻辑封装在方法内部,对外部调用者隐藏。
- 灵活性:可以返回不同类型的对象,这在继承和多态中尤其有用。
# 继承场景下的工厂方法
class USPerson(Person):
def __repr__(self):
return f"US Citizen: {self.first_name} {self.last_name}"
# USPerson 类继承了 from_string 类方法
us_person_data = "George Washington"
us_p = USPerson.from_string(us_person_data)
print(us_p)
# 输出: US Citizen: George Washington
# 注意:这里返回的是 USPerson 实例,而不是 Person 实例!
# 这是因为 'cls' 指的是调用该方法的类(USPerson)。
什么时候该用 @classmethod?
当你需要以下功能时,就应该使用 @classmethod:
- 你需要操作类本身的状态(类属性),而不是某个特定实例的状态。
- 你需要创建一个“工厂”,用于根据不同的输入或逻辑生成并返回类的实例。
- 你希望将一些与类相关联的逻辑组织在类内部,但又不需要访问实例数据,此时比静态方法更合适,因为它隐式地接收了类作为参数。
记住这个简单的规则:如果方法的逻辑依赖于类本身,而不是类的实例,那么它就是一个很好的候选者,可以声明为 @classmethod。

