HOWTO · Python
Python 中的类方法
本教程讨论 Python 中的类方法。
本页内容
类是创建对象的蓝图,但它也可以保存属于整个类的信息。类方法绑定到类本身,而不是某个特定实例。无需先创建对象,就可以通过类名调用它。按照惯例,Python 将类对象作为第一个参数传入,这个参数通常命名为 cls。
当操作需要类级配置、需要以对子类友好的方式创建对象,或者概念上描述的是类型而不是单个对象时,应使用类方法。实例方法则接收 self,因此处理的是某个实例的数据。静态方法不会自动接收 self 或 cls;它只是以类为命名空间的工具函数,所需的内容必须显式传入。
通常应写作 @classmethod。Python 也提供 classmethod() 内置函数,用于转换已有函数;维护旧代码时了解它会很有帮助。两种形式都会创建一个描述符,在调用时提供类对象。它们不提供传统的方法重载:在同一个类中定义两个同名方法会使后一个定义替换前一个定义。
在 Python 中使用 classmethod() 函数创建类方法
classmethod(function) 会将函数转换为类方法。在这种旧式写法中,先定义函数,再将转换后的结果赋回类属性。当以 Emp.dept_name() 调用该方法时,第一个参数接收的是 Emp。
class Emp:
dept = "python"
def dept_name(cls):
print(f"Department: {cls.dept}")
Emp.dept_name = classmethod(Emp.dept_name)
Emp.dept_name()
输出:
Department: python
这种形式是有效的 Python,但赋值语句将函数定义与其作为类方法的事实分开了。新代码中更推荐使用装饰器,因为读者能直接在函数上方看到这一意图。转换后的方法仍可通过实例调用,但它接收的是该实例的类,而不是实例本身。如果操作必须读取或修改每个对象的属性,应选择实例方法。
在 Python 中使用 @classmethod 装饰器创建类方法
将 @classmethod 直接放在函数定义上方。Python 会将类绑定到 cls,因此可通过 cls.attribute 访问共享属性。与硬编码 Emp 相比,使用 cls 在子类继承该方法时尤为重要:Python 会自动传入子类,子类也可以提供自己的类状态值。
class Emp:
dept = "python"
@classmethod
def dept_name(cls):
print(f"Department: {cls.dept}")
Emp.dept_name()
输出:
Department: python
常见用法是替代构造函数:验证或解析输入,然后返回 cls(...)。最后这一调用会保留实际调用的类型。例如,下方调用 Manager.from_str() 会返回 Manager;若在方法中写 Emp(...),则始终会返回 Emp。如果后续代码需要数值数据,请有意识地转换年龄等值;本示例仅显示解析出的文本,因此保留为文本。
class Emp:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_str(cls, sample):
if sample.count(",") != 1:
raise ValueError("expected 'name,age'")
name, age = sample.split(",")
return cls(name, age)
class Manager(Emp):
pass
employee = Emp.from_str("Matt,15")
manager = Manager.from_str("Ada,30")
print(type(employee).__name__, employee.name, employee.age)
print(type(manager).__name__, manager.name, manager.age)
输出:
Emp Matt 15
Manager Ada 30
解析器存在输入边界:它要求恰好一个逗号,因此没有逗号或含有额外逗号的值都无法提供恰好两个字段。调用方可能提供用户输入、文件或网络数据时,应明确这一行为。这里的类方法会抛出清晰的 ValueError,而不是返回部分初始化的对象。若无效记录应被跳过或报告而非中止程序,请在应用边界捕获该异常。
class Emp:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_str(cls, sample):
# boundary case: require exactly a name and an age
if sample.count(",") != 1:
raise ValueError("expected 'name,age'")
parts = sample.split(",")
if not all(parts):
raise ValueError("expected 'name,age'")
return cls(*parts)
try:
Emp.from_str("Matt,15,extra")
except ValueError as error:
print(f"Invalid record: {error}")
输出:
Invalid record: expected 'name,age'
类方法可通过 cls 读取或更新类属性,子类可以继承它,也可以覆盖这些属性。这种共享状态不同于每个实例的状态:赋值 self.dept 会创建或修改实例属性,而赋值 cls.dept 会修改被调用类上的属性。使用可变类属性时务必谨慎,因为使用同一类属性的所有实例都会观察到同一个对象。
当函数仅为组织而放在类附近,却不需要类状态或感知子类的构造方式时,使用静态方法。需要 self 时使用实例方法。当类本身是有意义的输入时,尤其是工厂方法、了解配置的辅助函数和替代构造函数,应使用类方法。这一区分能让方法签名与实际需求一致,并使继承行为更可预测。