← 返回题目列表

Python 中 @staticmethod、@classmethod 和 @property 有什么区别?

高频 中等 第 12 / 21 题 更新于 2026/07/27
staticmethodclassmethodproperty描述符

简化版

@staticmethod 定义不需要访问实例和类的工具方法;@classmethod 第一个参数是类对象 cls,常用于工厂方法或操作类级状态;@property 把方法包装成属性访问,常用于只读计算属性或受控访问。

详细版

示例:

class User:
    species = "human"

    def __init__(self, name):
        self.name = name

    @staticmethod
    def is_valid_name(name):
        return bool(name and name.strip())

    @classmethod
    def anonymous(cls):
        return cls("anonymous")

    @property
    def display_name(self):
        return self.name.title()

区别:

装饰器第一个参数适用场景
@staticmethod没有自动传入参数放在类命名空间下的工具函数
@classmethod自动传入类对象 cls工厂方法、访问类属性、支持继承多态
@property自动传入实例 self像属性一样访问方法结果

调用:

User.is_valid_name("Tom")
User.anonymous()
user = User("tom")
print(user.display_name)

完整版教学

一、普通实例方法先做参照

普通实例方法:

class User:
    def hello(self):
        return "hello"

调用:

u = User()
u.hello()

Python 会自动把实例 u 作为第一个参数传给 hello,也就是 self

所以:

u.hello()

可以理解为:

User.hello(u)

二、staticmethod:不需要 self,也不需要 cls

@staticmethod 适合和类概念相关,但不需要访问实例或类状态的方法。

class Email:
    @staticmethod
    def is_valid(address):
        return "@" in address

它可以通过类调用:

Email.is_valid("a@example.com")

也可以通过实例调用,但不会自动传入 self

email = Email()
email.is_valid("a@example.com")

如果这个函数和类关系不大,直接写成模块级函数也可以。不要为了“看起来面向对象”把所有工具函数都塞进类里。

三、classmethod:拿到的是类对象

@classmethod 的第一个参数通常命名为 cls

class User:
    def __init__(self, name):
        self.name = name

    @classmethod
    def from_dict(cls, data):
        return cls(data["name"])

调用:

u = User.from_dict({"name": "Tom"})

这里 clsUser 类本身。

为什么不用 User(...),而用 cls(...)

因为这能支持继承:

class Admin(User):
    pass

admin = Admin.from_dict({"name": "Root"})
print(type(admin))  # <class '__main__.Admin'>

如果写死 User(...),子类调用工厂方法时就不能返回子类对象。

四、property:方法属性化

@property 可以让方法像属性一样访问:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14 * self.radius * self.radius

调用:

c = Circle(10)
print(c.area)

注意是 c.area,不是 c.area()

它适合表达“看起来像属性,但需要计算或控制”的值。

五、property setter

property 还可以配合 setter 做受控赋值:

class User:
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("age must be positive")
        self._age = value

这样外部仍然写:

user.age = 18

但赋值会经过校验逻辑。

六、三者如何选择

可以用下面方式判断:

  • 方法需要访问实例数据:普通实例方法。
  • 方法不需要实例,也不需要类:staticmethod 或模块函数。
  • 方法需要类对象,尤其要支持继承构造:classmethod
  • 方法表示对象属性,但需要计算或校验:property

很多面试会追问 classmethodstaticmethod 的差异。关键点是 classmethod 会自动接收 cls,因此能访问类属性,也更适合做可继承的工厂方法。

七、常见误区与追问

易错点:三者的核心不是“能不能通过类调用”,而是 Python 自动帮你绑定了什么:实例方法绑定 self,类方法绑定 cls,静态方法什么都不绑定,属性访问绑定实例并调用 getter。

  • 误区:staticmethod 不能通过实例调用。 它可以通过实例调用,只是不会自动传入实例;如果方法完全不需要实例和类状态,放成模块函数也常常更直观。
  • 追问:classmethod 为什么适合做工厂方法? 因为它拿到的是 cls,子类调用时 cls 是子类,返回 cls(...) 能保留继承多态;写死 User(...) 会破坏子类构造。
  • 误区:property 只是为了少写括号。 它更重要的价值是把“属性访问”背后的计算、缓存或校验封装起来,让外部 API 保持 obj.attr 的稳定形态。
  • 追问:property setter 里为什么常写 _age 而不是 age 如果 setter 内部再写 self.age = value,会再次触发 setter,形成递归;写 self._age 是把真实存储放到另一个实例属性里。
  • 误区:类属性只能用 classmethod 访问。 实例方法也能通过 self.__class__ 或类名访问类属性,但语义不如 classmethod 直接;需要表达“这是类级逻辑”时应使用 cls
  • 追问:这三者和描述符有什么关系? 函数、staticmethodclassmethodproperty 都参与属性访问协议;区别在于描述符返回的是绑定方法、原函数、绑定到类的方法,还是 getter 的计算结果。

八、加强记忆

staticmethod 不自动传 self/cls,像放在类里的工具函数;classmethod 自动传 cls,适合工厂方法和类级逻辑;property 把方法变成属性访问,适合计算属性和受控赋值。判断时就问:这个方法到底需要实例、类,还是只需要一个命名空间?