写过几年 Python 的人大概都遇到过这样的纠结时刻------明明是个类里的方法,却不知道该不该加 self,加了 self 又觉得用不上,索性想偷懒直接写成普通函数。这种犹豫背后,其实藏着 Python 面向对象设计里一个挺有意思的问题:方法到底该跟谁绑定 。是绑定给某个具体的实例,还是绑定给整个类,又或者压根不需要绑定?@classmethod 和 @staticmethod 这两个装饰器,正是为了回答这个问题而生的。
三兄弟的家族谱系
Python 里的方法其实有三种身份,可以想象成一个小家族。
实例方法 是老大,最常见也最朴素,第一个参数永远是 self,代表调用它的那个具体对象。你写 dog.bark(),Python 在背后悄悄把 dog 这个实例塞进了 bark 方法的第一个位置。
类方法 是老二,用 @classmethod 装饰,第一个参数是 cls,代表的不是某个实例,而是类本身。不管你是通过类名调用还是通过实例调用,Python 都会把类塞进去,而且这个类还会随着继承关系动态变化------这一点后面会细讲,是理解 classmethod 的灵魂所在。
静态方法 是老三,用 @staticmethod 装饰,它跟前两位完全不是一路人。它不接收 self,也不接收 cls,本质上就是一个被塞进类命名空间里"寄养"的普通函数,跟这个类的实例状态、类状态都没有天然的联系。
用一张图理一理三者的定位关系。

绑定规则:谁在第一个坑位
Python 的描述符协议决定了这套自动传参的魔法。用几行伪公式来描述会清楚很多。
普通实例方法的调用等价于
instance.method(∗args)⇒method(instance,∗args)
类方法的调用等价于
ClassName.classmethod_func(∗args)⇒classmethod_func(ClassName,∗args)
静态方法的调用则完全没有这层自动绑定
instance.staticmethod_func(∗args)⇒staticmethod_func(∗args)
看代码会更直观。
python
class Pizza:
def __init__(self, radius, toppings):
self.radius = radius
self.toppings = toppings
def area(self):
# 实例方法:能访问 self.radius
return 3.14 * self.radius ** 2
@classmethod
def margherita(cls):
# 类方法:cls 就是 Pizza(或者调用它的子类)
return cls(12, ['cheese', 'tomato'])
@staticmethod
def validate_topping(topping):
# 静态方法:跟具体实例、具体类都没关系
return topping in ['cheese', 'pepperoni', 'mushroom']
area 需要知道是谁 的半径,所以离不开 self。validate_topping 只是校验一个字符串,跟"谁"完全无关,用 staticmethod 最合适。而 margherita 有点特殊------它是在造一个新对象,但又不想把类名写死,这就是 classmethod 真正大放异彩的场景。
继承场景才是分水岭
如果只是单个类使用,classmethod 和 staticmethod 的差别其实不算太致命,很多人图省事甚至会混用。但一旦引入继承,两者的差距就彻底拉开了。
来看一个经典的对比实验。
python
class Pizza:
def __init__(self, size):
self.size = size
@classmethod
def create_large(cls):
return cls(20) # 注意这里用的是 cls,不是 Pizza
@staticmethod
def create_large_static():
return Pizza(20) # 这里硬编码了 Pizza
class DeepDishPizza(Pizza):
pass
p1 = DeepDishPizza.create_large()
p2 = DeepDishPizza.create_large_static()
print(type(p1)) # <class '__main__.DeepDishPizza'>
print(type(p2)) # <class '__main__.Pizza'> ------ 出问题了!
看到没有,问题就出在这儿。DeepDishPizza 明明调用的是继承来的方法,理应得到一个 DeepDishPizza 实例,可 create_large_static 因为把类名写死成了 Pizza,返回的对象类型完全不对,子类的身份被生生抹掉了。而 create_large 用的是 cls,Python 会自动把调用它的那个类(这里是 DeepDishPizza)传进去,结果就完全符合预期。
这正是 classmethod 最核心的价值------支持多态构造 。它常被用来写工厂方法,尤其是那种需要根据不同输入构造对象的场景,比如 from_json、from_dict、from_config 这类命名的类方法在各种库里随处可见,背后逻辑都是同一套。

该用哪个:一张对照表说清楚
纠结的时候,不妨对着下面这张表自问几个问题,答案基本就出来了。
| 判断维度 | 实例方法 | @classmethod | @staticmethod |
|---|---|---|---|
| 第一个参数 | self(实例) |
cls(类) |
无自动参数 |
| 能否访问实例属性 | 能 | 不能 | 不能 |
| 能否访问/修改类属性 | 能(间接) | 能,且随子类变化 | 不能(除非显式写类名) |
| 典型用途 | 操作具体对象的状态 | 替代构造函数、工厂方法、修改类级配置 | 逻辑相关但无状态依赖的工具函数 |
| 继承时的行为 | 正常多态 | 动态绑定到调用它的子类,支持多态构造 | 行为固定,若硬编码类名会破坏多态 |
| 调用方式 | obj.method() |
Class.method() 或 obj.method() |
Class.method() 或 obj.method() |
简单归纳一下判断思路。
如果方法需要知道自己是造给哪个类的 ,比如某种工厂方法、备用构造器、或者需要读写类级别的共享状态(像计数器、配置项),选 classmethod。
如果方法压根不关心类是谁、实例是谁 ,纯粹是个逻辑上归属于这个类、但独立运作的小工具函数,比如参数校验、格式转换、简单的数学计算,选 staticmethod,把它当成一个"住在类里的普通函数"就行。
如果方法要操作某个具体对象的私有状态,那自然是普通实例方法的地盘,跟另外两位没什么关系。
几个容易踩的坑
有个误区特别常见------不少人觉得 staticmethod 就是"类方法的简化版",其实两者压根不是一回事,staticmethod 从设计初衷上就没打算跟类或实例产生绑定关系,它更像是借用类的命名空间做个归类整理,本质仍是一个自由散漫的函数。
另一个坑出现在类变量的操作上。如果你想写一个方法来修改类级别的共享状态(比如维护一个实例计数器),千万别用 staticmethod,因为它拿不到 cls,没法优雅地访问类属性;这时候 classmethod 才是正解。
python
class Counter:
count = 0
@classmethod
def increment(cls):
cls.count += 1 # 通过 cls 直接操作类属性,干净利落
还有个细节,classmethod 在多重继承或者 super() 链条比较复杂的情况下,cls 的指向可能会让人一时反应不过来,建议真遇到复杂继承体系时打印一下 cls.__name__ 确认,别凭直觉猜。
写在最后
说到底,classmethod 和 staticmethod 的区别不是语法层面的花活,而是设计哲学上的分歧------前者关心类的身份 ,天然适配继承和多态;后者压根不关心身份,图的是把相关逻辑归拢在一起,写起来清爽。下次再纠结要不要加装饰器时,不妨先问自己一句,这个方法在不同的子类里跑起来,结果该不该不一样 。答案是"该",用 classmethod;答案是"不该,压根用不上类和实例的信息",那就大大方方用 staticmethod。想清楚这层逻辑,剩下的就只是敲代码的事了。