该用哪种方法,只取决于方法需要什么:当前实例、类,还是都不需要。选错了,子类会悄无声息地出问题。
方法有三种,怎么选不是风格问题,而是看这个方法实际需要什么。
- 实例方法需要这个具体的对象。
- 类方法(
classmethod)需要类。 - 静态方法(
staticmethod)两者都不需要。
一个类里的三种方法
from datetime import date
class Employee:
raise_pct = 1.05
def __init__(self, name, salary):
self.name, self.salary = name, salary
def give_raise(self): # instance method: needs this employee
self.salary = round(self.salary * self.raise_pct, 2)
@classmethod
def from_csv(cls, line): # classmethod: needs the class
name, salary = line.split(',')
return cls(name, float(salary))
@staticmethod
def is_payday(d): # staticmethod: needs neither
return d.day == 25
e = Employee.from_csv('Ada,50000')
e.give_raise()
print(e.name, e.salary)
print(Employee.is_payday(date(2026, 9, 25)), Employee.is_payday(date(2026, 9, 3)))
输出:
Ada 52500.0
True False
from_csv 是类方法的经典用法:备选构造函数。Python 每个类只有一个 __init__,所以其他构建对象的方式都写成类方法。
为什么 from_csv 用 cls
这是关键所在,也是类方法不只是“恰好收到了类的静态方法”的原因。
# why from_csv uses cls and not Employee
class Contractor(Employee):
raise_pct = 1.10
c = Contractor.from_csv('Grace,60000')
print(type(c).__name__, c.name, c.salary)
c.give_raise()
print('after raise:', c.salary)
输出:
Contractor Grace 60000.0
after raise: 66000.0
Contractor.from_csv 生成的是 Contractor,不是 Employee,加薪也用的是 1.10。没有人在 Contractor 上写过 from_csv——cls 之所以是 Contractor,是因为调用就是在它上面发起的。
把类名写死,这就不灵了:
# hard-coding the class breaks the subclass
class Broken(Employee):
@classmethod
def from_csv(cls, line):
name, salary = line.split(',')
return Employee(name, float(salary)) # wrong: always the base class
print(type(Broken.from_csv('Alan,70000')).__name__)
输出:
Employee
Broken.from_csv 返回的是 Employee。每个子类都悄悄拿到了错误的类型,而且不会抛任何异常。备选构造函数一律用 cls。
各自收到了什么
class Show:
def instance(self): return f"got {type(self).__name__} instance"
@classmethod
def klass(cls): return f"got the class {cls.__name__}"
@staticmethod
def static(): return "got nothing"
s = Show()
print(s.instance())
print(Show.klass())
print(Show.static())
输出:
got Show instance
got the class Show
got nothing
什么时候用静态方法
很少。老实说:当一个普通函数就够用,但你想把它放进相关类的命名空间时。is_payday 既不碰某个员工,也不碰类——它是一个关于日期的函数。
如果静态方法跟这个类也没什么关系,它就该是模块级函数。放进类里,除了名字变长,什么也得不到。
要点
- 读取或修改当前对象的,用实例方法。
- 备选构造函数用类方法,并且用
cls,这样子类才能正常工作。 - 方法两者都不需要,而且和类放在一起确实有帮助时,才用静态方法。
- 在备选构造函数里写死类名是个 bug,要等到有人继承你的类时才会暴露。