Python 的 @classmethod 和 @staticmethod:什么时候用哪个,一次说清楚
写 Python 类的时候,除了普通的实例方法,还有两种带装饰器的方法:@classmethod 和 @staticmethod。
刚接触的时候很容易搞混——“不都是类里的方法吗,为什么还要分三种?”
其实区别很简单:实例方法要操作对象,类方法要操作类,静态方法只是”恰好放在这个类里的普通函数”。
先复习一下实例方法:带 self 的那个
1 2 3 4 5 6 7 8
| class Person: species = "人类" def __init__(self, name): self.name = name def greet(self): return f"你好,我是 {self.name}"
|
实例方法的特点:
- 第一个参数是
self,指向具体的对象
- 能访问对象的属性(
self.name)
- 能访问类的属性(
self.species 或 Person.species)
- 必须通过对象调用
实例方法处理的是”每个对象各自不同的数据”,比如每个人的名字不同。
类方法:@classmethod,操作的是类本身
类方法的第一个参数是 cls,指向类本身,而不是某个具体对象。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Person: count = 0 def __init__(self, name): self.name = name Person.count += 1 @classmethod def get_count(cls): return cls.count
Person.get_count()
|
类方法能访问和修改类属性(所有对象共享的数据),但不能访问实例属性(每个对象各自的数据)。
什么时候用类方法?三种最常见的场景:
场景1:工厂方法——用不同方式创建对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from datetime import datetime
class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def from_string(cls, date_str): """从 '2024-01-15' 这样的字符串创建日期对象""" year, month, day = map(int, date_str.split("-")) return cls(year, month, day) @classmethod def today(cls): """创建今天的日期""" now = datetime.now() return cls(now.year, now.month, now.day)
d1 = Date.from_string("2024-01-15") d2 = Date.today()
|
这种”替代构造函数”的模式,是类方法最典型的用途。
场景2:操作类属性(共享状态)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class GameConfig: difficulty = "normal" @classmethod def set_difficulty(cls, level): valid = ["easy", "normal", "hard"] if level in valid: cls.difficulty = level @classmethod def get_difficulty(cls): return cls.difficulty
GameConfig.set_difficulty("hard") print(GameConfig.get_difficulty())
|
场景3:继承场景下,cls 会自动指向子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Animal: @classmethod def create(cls, name): return cls(name)
class Dog(Animal): def __init__(self, name): self.name = name
class Cat(Animal): def __init__(self, name): self.name = name
dog = Dog.create("旺财") cat = Cat.create("咪咪")
|
如果在类方法里硬编码了类名(比如 return Animal(name)),子类调用的时候就会返回父类的实例,而不是子类的。用 cls 而不是写死类名,是类方法支持继承的关键。
静态方法:@staticmethod,就是普通函数放在类里
静态方法没有 self 也没有 cls,它就是个普通函数,只是恰好在类的命名空间里。
1 2 3 4 5 6
| class MathUtils: @staticmethod def add(a, b): return a + b
MathUtils.add(2, 3)
|
静态方法不能访问实例属性,也不能访问类属性(除非把类名传进去,但那样很别扭)。
什么时候用静态方法?它的价值在于”归类”——把相关但不需要访问类/实例状态的函数放在一起。
场景1:工具函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Validator: @staticmethod def is_email(email): import re pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$' return bool(re.match(pattern, email)) @staticmethod def is_phone(phone): import re return bool(re.match(r'^1[3-9]\d{9}$', phone))
Validator.is_email("test@example.com") Validator.is_phone("13812345678")
|
如果把这两个函数放在模块顶层也行,但放在 Validator 类里,代码组织更清晰——别人一看就知道”这些是验证相关的工具”。
场景2:数据转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class UnitConverter: @staticmethod def celsius_to_fahrenheit(c): return c * 9/5 + 32 @staticmethod def km_to_miles(km): return km * 0.621371 @staticmethod def bytes_to_human(size): for unit in ['B', 'KB', 'MB', 'GB']: if size < 1024: return f"{size:.1f} {unit}" size /= 1024 return f"{size:.1f} TB"
UnitConverter.celsius_to_fahrenheit(25)
|