文章目录

Python 缓存装饰器:functools.lru_cache 的应用

发布于 2026-04-18 12:17:17 · 浏览 11 次 · 评论 0 条

Python 缓存装饰器:functools.lru_cache 的应用

在 Python 开发中,重复计算是导致程序运行缓慢的常见原因之一。如果函数的输入参数相同,且返回结果固定,重复执行该函数会浪费宝贵的 CPU 时间。functools.lru_cache 装饰器是 Python 标准库中解决此问题的利器,它能以极低的代码成本实现缓存机制,显著提升程序性能。


1. 理解缓存机制的核心逻辑

缓存本质上是用“空间换时间”。当程序第一次调用带有特定参数的函数时,lru_cache 会记录下参数和计算结果。后续再次使用相同参数调用时,程序直接从内存中读取结果,跳过计算过程。

为了更直观地理解这一流程,可以参考以下执行逻辑:

graph LR A["Start: Call Function(x, y)"] --> B{Is Result in Cache?} B -- Yes --> C["Return Cached Result"] B -- No --> D["Execute Function Logic"] D --> E["Save Result to Cache"] E --> C

2. 基础用法:加速递归计算

递归函数(如计算斐波那契数列)是缓存技术最典型的应用场景。没有缓存时,递归会产生大量重复计算;引入 lru_cache 后,复杂度从指数级降低为线性级。

  1. 打开 Python 编辑器或 IDE。
  2. 导入 functools 模块中的 lru_cache
  3. 编写 一个计算斐波那契数列的函数,并在其上方添加 @lru_cache 装饰器。
from functools import lru_cache

@lru_cache(maxsize=None)  # maxsize=None 表示缓存大小无限制
def fib(n: int) -> int:
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)
  1. 运行 代码并计算 fib(40)
  2. 观察 执行速度。在有缓存的情况下,计算几乎是瞬时的。

3. 核心参数配置

lru_cache 提供了两个主要参数来控制缓存行为,合理配置能避免内存浪费。

3.1 设置缓存容量

默认情况下,maxsize=128。这意味着缓存只能存储最近的 128 次调用结果。当新的调用发生时,如果缓存已满,最久未使用的结果会被丢弃(LRU 策略)。

  1. 修改 装饰器参数。例如,设置 maxsize=32
@lru_cache(maxsize=32)
def my_process(data):
    # 模拟耗时操作
    return data * 2
  1. 测试 缓存淘汰。循环调用 函数超过 32 次并传入不同的参数,最早的缓存结果将自动失效。

3.2 区分数据类型

默认情况下,typed=False。这意味着参数 3(整数)和 3.0(浮点数)被视为同一个键,会命中同一个缓存。

  1. 添加 typed=True 参数。
  2. 强制 缓存区分不同数据类型的参数。
@lru_cache(maxsize=None, typed=True)
def check_type(value):
    print(f"Calculating with {value} of type {type(value)}")
    return value
  1. 依次执行 check_type(1)check_type(1.0)
  2. 观察 控制台输出。你会发现函数被打印了两次,说明两次调用被视为不同的请求,未命中对方的缓存。

4. 监控与清理缓存

在实际应用中,特别是长期运行的服务,监控缓存命中率对于性能调优至关重要。

  1. 调用 cache_info() 方法查看缓存统计信息。
# 假设已经执行了多次 fib() 调用
print(fib.cache_info())

输出结果类似 CacheInfo(hits=10, misses=5, maxsize=128, currsize=5)

  • hits:缓存命中次数(直接返回结果的次数)。
  • misses:缓存未命中次数(实际执行函数的次数)。
  • currsize:当前缓存中存储的结果数量。
  1. 执行 cache_clear() 方法手动清空缓存。
fib.cache_clear()

此操作会重置所有统计信息并释放内存,通常用于在程序运行到一个新阶段时,丢弃旧数据以避免内存占用过高。


5. 实战场景:优化 I/O 密集型任务

除了计算密集型任务,lru_cache 也能有效优化涉及数据库查询或网络请求的 I/O 密集型操作。注意:缓存的前提是“相同输入必然导致相同输出”。

  1. 模拟 一个耗时的数据获取函数。
import time
from functools import lru_cache

@lru_cache(maxsize=100)
def get_user_data(user_id):
    print(f"Fetching data for user {user_id} from database...")
    time.sleep(1)  # 模拟 1 秒的网络延迟
    return {"id": user_id, "name": f"User_{user_id}"}
  1. 第一次调用 get_user_data(1),程序会暂停约 1 秒并打印日志。
  2. 第二次调用 get_user_data(1)
  3. 观察 结果:程序立即返回数据,且没有再次打印日志,证明直接从缓存读取。

6. 常见陷阱与注意事项

使用缓存时必须遵守哈希协议,否则会引发程序错误。

  1. 避免 传入可变参数(如列表、字典)。因为这些对象不可哈希,Python 无法将其作为缓存的键。

    • 错误示例:

      @lru_cache
      def process_data(items):  # items 如果是 list 会报错
          return sum(items)
      
      # process_data([1, 2, 3])  # 报错:TypeError: unhashable type: 'list'
  2. 解决 方法是将可变对象转换为不可变对象(如元组)。

    • 正确示例:

      @lru_cache
      def process_data(items):  # items 期望是 tuple
          return sum(items)
      
      process_data((1, 2, 3))  # 正常运行
  3. 牢记 缓存有效期。lru_cache 没有内置的“过期时间”参数。如果数据在一段时间后可能失效(例如获取的股票价格),需要在数据变更时显式调用 cache_clear(),或者考虑使用第三方库(如 cachetools)来实现 TTL(Time To Live)功能。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文