Skip to main content
Documents
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Functools -- cached_property

✅ 1. cached_property 是什么?

cached_property 是 Python 3.8+ 引入的装饰器:

from functools import cached_property

它用于 把类的方法变成只计算一次的属性,并把结果缓存下来。

换句话说:

  • 📌 第一次访问时运行函数、计算结果
  • 📌 后续访问不再执行函数,而是直接从缓存里取值
  • 📌 用于那些计算昂贵但又不想在初始化时执行的属性

📌 2. 最简单示例

from functools import cached_property
import time

class Data:
    @cached_property
    def expensive_value(self):
        print("计算中...")
        time.sleep(2)
        return 42

d = Data()
print(d.expensive_value)   # 会打印 "计算中...",然后输出 42
print(d.expensive_value)   # 直接返回 42,不再计算

👉 第二次访问时 不会再执行函数。

✅ 3. 为什么它优于 property?

普通 @property:

  • 每访问一次就计算一次 -> 慢
  • 不适合昂贵操作

@cached_property:

  • 只执行一次
  • 后面全部用缓存
  • 缓存的值存储在实例的 __dict__

🟣 4. cached_property 的内部机制(很重要)

访问 obj.attr 时:

  1. 第一次调用方法
  2. 执行方法体,得到结果
  3. 把结果放到 obj.__dict__['attr']
  4. 下次访问直接从 dict 中取值

所以你可以手动清除缓存:

del obj.expensive_value

下次访问会重新计算。

🟢 5. 实用场景(非常重要)

1. 数据库连接(懒加载)

class DB:
    @cached_property
    def engine(self):
        return create_engine(...)

访问 db.engine 才真正建立连接。

2. 配置文件读取

class Config:
    @cached_property
    def settings(self):
        with open("config.json") as f:
            return json.load(f)

不会每次都读文件。

3. Web 项目中缓存计算结果

class User:
    @cached_property
    def profile(self):
        return load_profile_from_db(self.id)

4. 大量计算(复杂统计、解析、分析)

class Stock:
    @cached_property
    def summary(self):
        return compute_summary(self.data)

🔥 6. 何时用 cached_property,而不是 cache?

场景 用 cached_property 用 cache
绑定到对象实例
想缓存方法结果(与 self 绑定)
想缓存普通函数
想缓存多种参数的结果 ✅(cache 支持不同参数缓存)
想懒加载属性

cached_property 是 instance-level cache cache 是 function-level cache

🟩 7. cached_property 清除缓存

del obj.expensive_value

再次访问会重新计算。

如果你想一次性清空全部 cached_property:

for key in list(obj.__dict__.keys()):
    del obj.__dict__[key]

🟥 8. 注意事项(非常关键)

❌ 不支持 async

@cached_property
async def value():   # 错误

不能这么写。

若需要 async 版,我能帮你写 async_cached_property。

❌ 不要用于可变对象(容易脏数据)

例如:

@cached_property
def data(self):
    return {"a": 1}

你修改它:

obj.data["a"] = 999

缓存不会刷新,小心副作用。

🟦 9. 高级用法:只读属性(不可覆盖)

如果希望防止用户覆盖 cached_property:

class Safe:
    @cached_property
    def value(self):
        return 123

    @value.setter
    def value(self, v):
        raise AttributeError("value is read-only")

🟣 10. 高级封装:带 logger 的 cached_property

from functools import cached_property
from loguru import logger

def logged_cached_property(func):
    @cached_property
    def wrapper(self):
        logger.debug(f"Computing {func.__name__}")
        return func(self)
    return wrapper

使用:

class Data:
    @logged_cached_property
    def info(self):
        return load_big_data()

🟩 11. 一屏掌握 cached_property

  • 用法:@cached_property
  • 目的:把计算一次的值缓存为属性
  • 首次访问:执行函数并缓存
  • 再次访问:直接读取缓存,不执行函数
  • 缓存位置:实例 __dict__
  • 清除缓存:del obj.attr
  • 适用场景:懒加载、昂贵计算、配置读取
  • 不适用:async、会变化的值、需要参数的函数