Python 内置类方法装饰器:@classmethod、@staticmethod、@property 到底怎么用
Python 内置类方法装饰器:@classmethod、@staticmethod、@property 到底怎么用?
一篇帮你彻底搞懂 Python 类里三个最常用装饰器的文章
写 Python 类的时候,你是不是经常看到这几个装饰器,却总是分不清它们到底有什么区别、什么时候该用哪个?
@classmethod@staticmethod@property
它们看起来都是加在方法上面的「帽子」,但背后的行为却大不相同。今天这篇文章,就带你一次性把它们搞清楚。
一、先看一张对比表
| 装饰器 | 第一个参数 | 调用方式 | 核心用途 |
|---|---|---|---|
@classmethod |
cls(类本身) |
类或实例 | 工厂方法,属于整个类型的行为 |
@staticmethod |
无 | 类或实例 | 与状态无关的方法 |
@property |
self(实例) |
像属性一样访问 | 把方法变成属性,控制读/写/删 |
下面我们一个一个拆开看。
二、@classmethod:属于「类」而不是「实例」的方法
普通方法的痛点
先看一个普通的鸭子类:
1 | class Duck: |
创建一只鸭子,调用它的方法:
1 | d = Duck('yellow') |
但如果你不创建实例,直接用类来调用 quack(),就会报错:
1 | Duck.quack() |
原因很简单:普通方法需要 self,也就是一个具体的实例。
用 @classmethod 突破这个限制
如果你想要一个方法,不需要实例就能调用,那就用 @classmethod:
1 | import random |
注意两点:
- 第一个参数是
cls,代表类本身,而不是self; - 通过
cls(color=color)来创建实例。
调用效果:
1 | d = Duck.create_random() |
甚至可以用实例来调用类方法,效果一样:
1 | d.create_random() |
什么时候用 @classmethod?
当你发现某个行为不属于某个具体实例,而是属于整个类型时,就用类方法。
最典型的场景就是工厂方法——像上面的 create_random(),它负责「生产」实例,而不是「操作」某个已有实例。
三、@staticmethod:与状态无关的方法
一个尴尬的方法
假设我们写了一个猫类:
1 | class Cat: |
get_sound() 这个方法,压根没用到 self 的任何东西。它不依赖 self.name,也不依赖任何实例状态。既然如此,为什么还要把 self 传进去呢?
用 @staticmethod 去掉 self
1 | class Cat: |
调用效果:
1 | c = Cat('Jack') |
静态方法不接收实例作为第一个位置参数,所以既可以通过实例调用,也可以通过类调用。
静态方法 vs 普通函数:怎么选?
既然静态方法不需要 self,那它跟写在类外面的普通函数有什么区别?答案是:几乎没区别。 那为什么要放在类里?
这里有三个判断依据:
- 如果静态方法特别通用、与类关系不大 → 改成普通函数更好;
- 如果静态方法与类关系密切 → 用静态方法更好;
- 相比普通函数,静态方法有先天优势:它能被子类继承和重写。
所以,把 get_sound() 放在 Cat 类里,是因为它跟「猫叫」这件事强相关;如果某个函数跟类八竿子打不着,那就别硬塞进类里。
四、@property:让方法「伪装」成属性
属性 vs 方法:两种接口
在一个类里,属性和方法的职责不同:
- 属性代表状态,通过
inst.attr直接访问; - 方法代表行为,通过
inst.method()调用。
但有时候,我们想要一个「计算出来的属性」——它看起来像属性,实际上背后是一段逻辑。这时候就用 @property。
基础用法
1 | import os |
调用时,就像用普通属性一样:
1 | p = FilePath('/tmp/foo.py') |
注意:调用时没有括号。basename 看起来是个属性,实际上是个方法。
进阶:控制写入和删除
@property 不只是读取,还支持自定义赋值和删除逻辑:
1 | class FilePath: |
效果:
1 | p = FilePath('/tmp/foo.py') |
通过 @property,你可以基于方法定义类属性,精确控制属性的读取、赋值和删除行为,灵活地实现动态属性。
一个重要的提醒
把方法改成属性后,它的使用接口会发生很大的变化。你需要学会判断,方法和属性分别适合什么样的场景。
书里举了一个很典型的反例:
假设你的类有个方法叫
get_latest_items(),调用它会请求外部服务的数十个接口,耗费 5~10 秒钟。那么这时,盲目把这个方法改成.latest_items属性就不太恰当。
为什么?因为人们在读取属性时,总是期望能迅速拿到结果;而调用方法则不一样——快点儿慢点儿都无所谓。
让自己设计的接口符合他人的使用预期,也是写代码时很重要的一环。
五、一张图总结
1 | 类的接口设计 |
六、记忆口诀
最后,送你一句口诀,帮你快速回忆:
@classmethod管「类型」,@staticmethod管「无关状态」,@property管「接口」。
- 想写工厂方法、操作整个类 →
@classmethod - 方法不需要
self、纯粹是个工具 →@staticmethod - 想让方法伪装成属性、控制读写 →
@property
搞懂这三个装饰器,你的 Python 类设计能力就上了一个台阶。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。也欢迎在评论区聊聊:你平时最常用哪个装饰器?有没有踩过 @property 的坑?





