装饰器 · cached_property

0. 一句话定位

维度 内容
作用对象 类实例方法
使用场景 属性访问
来源 标准库 functools.cached_property(3.8+)
语法形式 @cached_property

1. 做什么

第一次访问 obj.attr 时执行方法并把返回值写入实例 __dict__;之后访问直接读缓存,不再调用方法。适用于依赖 self、计算成本高、结果在实例生命周期内不变的数据。

2. 重点参数

参数 类型 默认值 作用 配置建议
func callable 被装饰方法 无参装饰器;方法首参为 self

无额外配置项;缓存键为属性名,存于实例字典。

3. 最小可运行示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from functools import cached_property

class DataSet:
def __init__(self, path):
self.path = path

@cached_property
def size(self):
print("computing size...")
with open(self.path, "rb") as f:
return len(f.read())

ds = DataSet("/etc/hosts")
print(ds.size) # computing size...
print(ds.size) # 直接返回缓存,无 printing

4. 常见变体

清除缓存(测试或数据变更后)

1
del ds.size  # 删除实例属性,下次访问重新计算

@property 对比

1
2
3
@property
def size_prop(self):
... # 每次访问都计算

5. 适用 / 不适用

适用

  • 依赖实例状态、首次计算后不变的属性(解析配置、统计量)
  • @lru_cache 更符合「每个实例一份缓存」语义

不适用

  • 底层数据会变而缓存不刷新 → 用 @property 或手动失效
  • 类级共享缓存、与 self 无关 → @lru_cache on 静态/模块函数
  • 需要 setter → 用 @property

6. 易踩坑

  • 缓存后不会self.path 等依赖自动更新;变更后须 del obj.attr
  • 不能用于self 的类方法(3.8+ 仅实例方法;类级见 classmethod + 其他模式)
  • 多线程首次并发访问可能重复计算(一般可接受);要严格单次用锁
  • __slots__ 联用时须确保 slotted 类支持写入该属性名

7. 近邻替代

替代 何时用
@property 每次访问都需最新值
@lru_cache 纯函数、参数可哈希、跨实例共享
手动 self._cache = None 需精细控制失效逻辑

8. 参考

-------------本文结束感谢您的阅读-------------