HOWTO · Python

Python 中的类方法

本教程讨论 Python 中的类方法。

本页内容

类是对象的蓝图,但也能保存由其创建的所有对象共享的数据和行为。Python 提供实例方法、类方法和静态方法,使代码能够明确操作需要与其中哪一层交互。

实例方法的第一个参数接收实例,按惯例命名为 self。当操作需要某个对象的数据(例如员工的姓名或年龄)时,应使用实例方法。类方法的第一个参数则接收类对象,按惯例命名为 cls。无需先创建实例即可通过类调用它,并且它能够读取或更新类级状态。

传入 cls 的值是实际用于调用的类,而不是任意实例。这一特点使类方法特别适合用作替代构造函数:方法可以验证输入并返回 cls(...),从而让子类得到自身的实例。静态方法既不接收 self,也不接收 cls;它只是恰好定义在类中的命名空间工具函数。

Python 支持两种创建类方法的方式:使用 classmethod() 转换已有函数,或使用 @classmethod 装饰器。对于新代码,装饰器通常更清晰,因为方法的绑定关系会在定义处直接体现。转换形式在维护已经包含普通函数的代码时仍然有用。

在 Python 中使用 classmethod() 函数创建类方法

classmethod() 函数接收一个函数并返回类方法描述符。先在类中定义函数,再将转换后的描述符赋回该属性。在此示例中,department_name 接收 Emp 作为 cls,因此在调用 Emp.department_name() 时能够获取共享的 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

当调用时 cls 为 Employee,因此 from_record() 返回 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 更直接,也让读者和维护者能够看清每个方法的依赖关系。