
本文介绍使用 python 3.8+ 的 `functools.cached_property` 装饰器,将耗时的类方法转为惰性求值、仅执行一次且自动缓存的属性,避免重复计算,兼顾性能与代码可读性。
在面向对象编程中,常遇到一类典型场景:某个计算逻辑开销大(如加载模型、解析大型配置、查询数据库),但结果在整个实例生命周期内恒定不变;同时该结果需被多个方法反复使用。若将其放在 __init__ 中,可能违反“延迟初始化”原则(例如依赖尚未就绪的外部资源);若每次调用都重新计算,则造成严重性能浪费。
Python 提供了简洁而地道的解决方案——functools.cached_property。它专为此类场景设计:将一个无参方法转换为线程安全、只计算一次、结果自动缓存于实例字典中的只读属性。首次访问时执行原方法并缓存返回值;后续访问直接返回缓存值,完全绕过函数调用开销。
以下是一个优化后的完整示例:
from functools import cached_property
from typing import List
class Example:
def __init__(self, data_source: str = "default") -> None:
self.data_source = data_source # 可用于构造时的轻量初始化
@cached_property
def my_value(self) -> int:
"""模拟耗时计算:仅在首次访问 self.my_value 时执行一次"""
print("→ 执行昂贵计算(仅一次)...")
# 此处可替换为真实耗时操作,如:
# - 加载预训练模型
# - 解析 GB 级 YAML 配置
# - 查询远程 API 并缓存响应
return sum(i * i for i in range(100_000)) # 模拟 CPU 密集型计算
def do_something_with_my_value(self) -> List[int]:
"""多次使用 my_value,但实际仅触发一次计算"""
return [x + self.my_value for x in range(1, 5)]
def another_method(self) -> str:
return f"基于 my_value({self.my_value}) 的衍生处理"使用效果演示:
ex = Example() print(ex.do_something_with_my_value()) # 输出前打印 "→ 执行昂贵计算(仅一次)..." print(ex.another_method()) # 不再打印,直接使用缓存值 print(ex.my_value) # 直接访问属性,仍为缓存值
✅ 关键优势:
- 零侵入性:无需修改调用方代码(self.my_value 语法保持不变,无需加括号);
- 类型友好:类型提示仍可准确标注返回类型(如 -> int),IDE 和静态检查工具完全支持;
- 内存隔离:缓存值存储在实例 __dict__ 中,不同实例互不干扰;
- 线程安全:内部使用锁保障多线程首次访问的原子性(CPython 下默认安全);
- 符合 Python 哲学:显式、简洁、可读性强,比手动实现 if not hasattr(self, '_my_value'): ... 更优雅。
⚠️ 注意事项:
- cached_property 仅适用于无参数、无副作用、结果确定性的方法(即相同输入/状态必得相同输出);
- 缓存不可清除(除非手动 del obj.my_value),若需重算,请考虑 @property + 显式缓存控制,或改用 functools.lru_cache()(需确保方法可哈希);
- Python
- 不适用于需要序列化(如 pickle)的场景——因缓存值不参与序列化,反序列化后会重新计算。
总结而言,@cached_property 是 Python 中解决“一次性惰性计算 + 多次复用”问题的标准答案。它让代码既高效又清晰,在数据处理、机器学习封装、配置中心客户端等高频场景中极具实用价值。










