Python 中 @staticmethod、@classmethod 和 @property 有什么区别?
简化版
@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"})
这里 cls 是 User 类本身。
为什么不用 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。
很多面试会追问 classmethod 和 staticmethod 的差异。关键点是 classmethod 会自动接收 cls,因此能访问类属性,也更适合做可继承的工厂方法。
七、常见误区与追问
易错点:三者的核心不是“能不能通过类调用”,而是 Python 自动帮你绑定了什么:实例方法绑定
self,类方法绑定cls,静态方法什么都不绑定,属性访问绑定实例并调用 getter。
- 误区:
staticmethod不能通过实例调用。 它可以通过实例调用,只是不会自动传入实例;如果方法完全不需要实例和类状态,放成模块函数也常常更直观。 - 追问:
classmethod为什么适合做工厂方法? 因为它拿到的是cls,子类调用时cls是子类,返回cls(...)能保留继承多态;写死User(...)会破坏子类构造。 - 误区:
property只是为了少写括号。 它更重要的价值是把“属性访问”背后的计算、缓存或校验封装起来,让外部 API 保持obj.attr的稳定形态。 - 追问:
propertysetter 里为什么常写_age而不是age? 如果 setter 内部再写self.age = value,会再次触发 setter,形成递归;写self._age是把真实存储放到另一个实例属性里。 - 误区:类属性只能用
classmethod访问。 实例方法也能通过self.__class__或类名访问类属性,但语义不如classmethod直接;需要表达“这是类级逻辑”时应使用cls。 - 追问:这三者和描述符有什么关系? 函数、
staticmethod、classmethod、property都参与属性访问协议;区别在于描述符返回的是绑定方法、原函数、绑定到类的方法,还是 getter 的计算结果。
八、加强记忆
staticmethod 不自动传 self/cls,像放在类里的工具函数;classmethod 自动传 cls,适合工厂方法和类级逻辑;property 把方法变成属性访问,适合计算属性和受控赋值。判断时就问:这个方法到底需要实例、类,还是只需要一个命名空间?