7.3 KiB
sources, brief
| sources | brief | ||
|---|---|---|---|
|
staticmethod 与 classmethod 是用于定义类级方法行为的 Python 内置装饰器。 |
Python staticmethod 与 classmethod
@staticmethod 与 @classmethod 是 Python 类定义中常用的内置装饰器,用于改变方法与实例、类之间的绑定方式。它们都把函数放在类的命名空间中,但对调用时自动传入的参数有不同规则。
相关来源:summaries/05_Decorated_methods
核心区别
在普通实例方法中,第一个参数通常是 self,表示当前实例:
class Foo:
def bar(self):
print(self)
调用实例方法时:
f = Foo()
f.bar()
Python 会自动把实例 f 作为第一个参数传入 bar()。
而 @staticmethod 和 @classmethod 改变了这种默认绑定方式:
class Foo:
@staticmethod
def spam(a):
...
@classmethod
def grok(cls, a):
...
@staticmethod:不会自动接收实例self,也不会自动接收类cls。@classmethod:自动接收类对象作为第一个参数,通常命名为cls。
相关概念:Python面向对象编程、Python装饰器、self与cls
@staticmethod:静态方法
@staticmethod 用于定义静态方法。静态方法属于类,但不依赖具体实例,也不依赖类对象本身。
示例:
class Foo:
@staticmethod
def bar(x):
print('x =', x)
Foo.bar(2)
输出:
x = 2
这里调用 Foo.bar(2) 时,Python 不会额外传入 self 或 cls,参数 x 就是调用者显式传入的 2。
静态方法适合什么场景
静态方法适合表示“逻辑上属于这个类,但不需要访问实例状态或类状态”的函数。
常见用途包括:
- 类内部的辅助函数;
- 与类相关的工具逻辑;
- 管理实例创建、资源、持久化、锁等支持代码;
- 某些设计模式中的类级工具函数。
例如,一个类可能需要一些格式化、校验、转换等辅助逻辑。如果这些逻辑不需要访问 self 或 cls,可以定义为静态方法。
@classmethod:类方法
@classmethod 用于定义类方法。类方法调用时会自动接收类对象作为第一个参数,通常命名为 cls。
示例:
class Foo:
def bar(self):
print(self)
@classmethod
def spam(cls):
print(cls)
调用:
f = Foo()
f.bar() # 打印实例 f
Foo.spam() # 打印类 Foo
普通实例方法中的 self 指向对象实例;类方法中的 cls 指向类本身。
这使得类方法可以访问类对象,并用类对象创建新实例或操作类级状态。
类方法最常见用途:替代构造器
在 summaries/05_Decorated_methods 中,@classmethod 最重要的用途是定义“替代构造器”。
例如,普通构造器可能要求传入年月日:
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
如果希望提供一个“创建今天日期”的构造方式,可以写成类方法:
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def today(cls):
tm = time.localtime()
return cls(tm.tm_year, tm.tm_mon, tm.tm_mday)
调用:
d = Date.today()
这里 today() 不需要调用者手动准备年月日,而是由类方法内部根据当前时间构造对象。
相关概念:concepts/替代构造器、封装
为什么类方法适合继承
类方法的一个重要优点是继承友好。
如果在类方法中使用:
return cls(...)
而不是:
return Date(...)
那么当子类调用该方法时,cls 会自动变成子类。
示例:
class Date:
@classmethod
def today(cls):
tm = time.localtime()
return cls(tm.tm_year, tm.tm_mon, tm.tm_mday)
class NewDate(Date):
pass
d = NewDate.today()
此时 NewDate.today() 中的 cls 是 NewDate,所以返回的是 NewDate 实例,而不是写死的 Date 实例。
这正是 @classmethod 在对象构造中比静态方法或硬编码类名更灵活的地方。
相关概念:Python继承、面向对象设计
实践示例:Portfolio.from_csv()
在 summaries/05_Decorated_methods 的练习中,@classmethod 被用于重构 Portfolio 对象的创建过程。
原来的设计中,读取 CSV、解析数据、创建 Stock 对象、创建 Portfolio 对象的逻辑分散在外部函数中。这会导致责任不清晰。
改进后的设计是让 Portfolio 自己负责从 CSV 创建实例:
class Portfolio:
def __init__(self):
self.holdings = []
def append(self, holding):
if not isinstance(holding, stock.Stock):
raise TypeError('Expected a Stock instance')
self.holdings.append(holding)
@classmethod
def from_csv(cls, lines, **opts):
self = cls()
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float],
**opts)
for d in portdicts:
self.append(stock.Stock(**d))
return self
使用方式:
with open('Data/portfolio.csv') as lines:
port = Portfolio.from_csv(lines)
这个例子体现了类方法的典型价值:
- 将对象创建逻辑封装进类;
- 让外部代码不必知道类的内部构造过程;
- 保持类型检查和内部一致性;
- 使用
cls()支持未来的子类扩展。
相关概念:对象构造、类型检查、封装
选择 staticmethod 还是 classmethod
可以用以下判断标准:
使用 @staticmethod 的情况
当方法满足以下条件时,可以考虑静态方法:
- 逻辑属于这个类的语义范围;
- 不需要访问实例属性;
- 不需要访问类对象;
- 只是一个放在类中的辅助函数。
使用 @classmethod 的情况
当方法满足以下条件时,应优先考虑类方法:
- 需要知道当前调用的类;
- 需要创建当前类或子类的实例;
- 需要实现替代构造器;
- 需要与继承机制协同工作;
- 需要访问或修改类级状态。
对比总结
| 方法类型 | 装饰器 | 自动传入参数 | 常见用途 |
|---|---|---|---|
| 实例方法 | 无 | self,当前实例 |
操作实例状态 |
| 静态方法 | @staticmethod |
无 | 类相关辅助函数 |
| 类方法 | @classmethod |
cls,当前类 |
替代构造器、继承友好的类级逻辑 |
关键理解
staticmethod 和 classmethod 都是类定义中的方法组织工具,但它们表达的设计意图不同:
@staticmethod表示“这个函数与类有关,但不需要类或实例参与”;@classmethod表示“这个方法作用于类本身,并且可能需要根据当前类创建对象”。
在面向对象设计中,@classmethod 尤其适合把对象创建逻辑放回类内部,使代码更清晰、更封装,也更适合继承扩展。