Python 缓存装饰器:functools.lru_cache 的应用
在 Python 开发中,重复计算是导致程序运行缓慢的常见原因之一。如果函数的输入参数相同,且返回结果固定,重复执行该函数会浪费宝贵的 CPU 时间。functools.lru_cache 装饰器是 Python 标准库中解决此问题的利器,它能以极低的代码成本实现缓存机制,显著提升程序性能。
1. 理解缓存机制的核心逻辑
缓存本质上是用“空间换时间”。当程序第一次调用带有特定参数的函数时,lru_cache 会记录下参数和计算结果。后续再次使用相同参数调用时,程序直接从内存中读取结果,跳过计算过程。
为了更直观地理解这一流程,可以参考以下执行逻辑:
2. 基础用法:加速递归计算
递归函数(如计算斐波那契数列)是缓存技术最典型的应用场景。没有缓存时,递归会产生大量重复计算;引入 lru_cache 后,复杂度从指数级降低为线性级。
- 打开 Python 编辑器或 IDE。
- 导入
functools模块中的lru_cache。 - 编写 一个计算斐波那契数列的函数,并在其上方添加
@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)
- 运行 代码并计算
fib(40)。 - 观察 执行速度。在有缓存的情况下,计算几乎是瞬时的。
3. 核心参数配置
lru_cache 提供了两个主要参数来控制缓存行为,合理配置能避免内存浪费。
3.1 设置缓存容量
默认情况下,maxsize=128。这意味着缓存只能存储最近的 128 次调用结果。当新的调用发生时,如果缓存已满,最久未使用的结果会被丢弃(LRU 策略)。
- 修改 装饰器参数。例如,设置
maxsize=32。
@lru_cache(maxsize=32)
def my_process(data):
# 模拟耗时操作
return data * 2
- 测试 缓存淘汰。循环调用 函数超过 32 次并传入不同的参数,最早的缓存结果将自动失效。
3.2 区分数据类型
默认情况下,typed=False。这意味着参数 3(整数)和 3.0(浮点数)被视为同一个键,会命中同一个缓存。
- 添加
typed=True参数。 - 强制 缓存区分不同数据类型的参数。
@lru_cache(maxsize=None, typed=True)
def check_type(value):
print(f"Calculating with {value} of type {type(value)}")
return value
- 依次执行
check_type(1)和check_type(1.0)。 - 观察 控制台输出。你会发现函数被打印了两次,说明两次调用被视为不同的请求,未命中对方的缓存。
4. 监控与清理缓存
在实际应用中,特别是长期运行的服务,监控缓存命中率对于性能调优至关重要。
- 调用
cache_info()方法查看缓存统计信息。
# 假设已经执行了多次 fib() 调用
print(fib.cache_info())
输出结果类似 CacheInfo(hits=10, misses=5, maxsize=128, currsize=5):
hits:缓存命中次数(直接返回结果的次数)。misses:缓存未命中次数(实际执行函数的次数)。currsize:当前缓存中存储的结果数量。
- 执行
cache_clear()方法手动清空缓存。
fib.cache_clear()
此操作会重置所有统计信息并释放内存,通常用于在程序运行到一个新阶段时,丢弃旧数据以避免内存占用过高。
5. 实战场景:优化 I/O 密集型任务
除了计算密集型任务,lru_cache 也能有效优化涉及数据库查询或网络请求的 I/O 密集型操作。注意:缓存的前提是“相同输入必然导致相同输出”。
- 模拟 一个耗时的数据获取函数。
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}"}
- 第一次调用
get_user_data(1),程序会暂停约 1 秒并打印日志。 - 第二次调用
get_user_data(1)。 - 观察 结果:程序立即返回数据,且没有再次打印日志,证明直接从缓存读取。
6. 常见陷阱与注意事项
使用缓存时必须遵守哈希协议,否则会引发程序错误。
-
避免 传入可变参数(如列表、字典)。因为这些对象不可哈希,Python 无法将其作为缓存的键。
-
错误示例:
@lru_cache def process_data(items): # items 如果是 list 会报错 return sum(items) # process_data([1, 2, 3]) # 报错:TypeError: unhashable type: 'list'
-
-
解决 方法是将可变对象转换为不可变对象(如元组)。
-
正确示例:
@lru_cache def process_data(items): # items 期望是 tuple return sum(items) process_data((1, 2, 3)) # 正常运行
-
-
牢记 缓存有效期。
lru_cache没有内置的“过期时间”参数。如果数据在一段时间后可能失效(例如获取的股票价格),需要在数据变更时显式调用cache_clear(),或者考虑使用第三方库(如cachetools)来实现 TTL(Time To Live)功能。

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