Python 中的 `lru_cache` 详解
目录
- 核心特性
- 使用方法
- 1. 基本用法
- 2. 参数说明
- 缓存清理方法
- 1. 清空整个缓存
- 2. 手动淘汰旧缓存
- 实用技巧
- 1. 查看缓存状态
- 2. 类型敏感缓存
- 3. 缓存无参函数
- 完整示例
- 使用场景
- LRU Cache 在 API 中的清理操作影响范围
- 关键结论:
- 示例演示:API 中的缓存隔离
- 场景:两个 API 端点使用相同计算函数但不同缓存策略
- 测试步骤及结果:
- 重要注意事项:
- 最佳实践建议:
lru_cache
是 Python 标准库
functools
模块提供的装饰器,用于实现
LRU(Least Recently Used)缓存策略。它会自动缓存函数的计算结果,当使用相同的参数再次调用时直接返回缓存结果,避免重复计算。
核心特性
- LRU 策略:当缓存达到容量上限时,自动淘汰 最久未使用 的结果
- 线程安全:适合多线程环境
- 性能提升:特别适用于计算密集型函数
使用方法
1. 基本用法
from functools import lru_cache@lru_cache(maxsize=128) # 设置缓存容量
def factorial(n):print(f"计算 {n} 的阶乘")return 1 if n <= 1 else n * factorial(n-1)print(factorial(5)) # 首次计算,会递归调用
print(factorial(5)) # 直接返回缓存结果
输出:
计算 5 的阶乘
计算 4 的阶乘
计算 3 的阶乘
计算 2 的阶乘
计算 1 的阶乘
120
120 # 无计算过程输出
2. 参数说明
@lru_cache(maxsize=None, typed=False)
maxsize
:缓存容量(默认128)None
:无限缓存(慎用)0
:禁用缓存
typed
:是否区分参数类型(默认False
)True
:1
(int)和1.0
(float)视为不同参数
缓存清理方法
1. 清空整个缓存
factorial.cache_clear() # 清空所有缓存
2. 手动淘汰旧缓存
通过“伪调用”触发 LRU 淘汰:
@lru_cache(maxsize=3)
def square(x):return x * xsquare(1) # 缓存 [1]
square(2) # 缓存 [1, 2]
square(3) # 缓存 [1, 2, 3]
square(4) # 淘汰最旧的1 → 缓存 [2, 3, 4]
实用技巧
1. 查看缓存状态
print(square.cache_info())
输出示例:
CacheInfo(hits=3, misses=5, maxsize=3, currsize=3)
hits
:缓存命中次数misses
:缓存未命中次数currsize
:当前缓存数量
2. 类型敏感缓存
@lru_cache(typed=True)
def type_sensitive(x):return type(x)print(type_sensitive(1)) # <class 'int'>
print(type_sensitive(1.0)) # <class 'float'> (视为不同调用)
3. 缓存无参函数
@lru_cache()
def get_config():return load_from_database() # 只执行一次
完整示例
from functools import lru_cache
import time@lru_cache(maxsize=3)
def heavy_calculation(n):print(f"执行耗时计算: {n}")time.sleep(1)return n ** 2# 首次调用
print(heavy_calculation(2)) # 执行计算
print(heavy_calculation(3)) # 执行计算
print(heavy_calculation(2)) # 使用缓存# 触发缓存淘汰
print(heavy_calculation(4)) # 执行计算 → 缓存[2,3,4]
print(heavy_calculation(5)) # 执行计算 → 淘汰2 → 缓存[3,4,5]# 查看缓存状态
print(heavy_calculation.cache_info())
# 输出: CacheInfo(hits=1, misses=4, maxsize=3, currsize=3)# 清空缓存
heavy_calculation.cache_clear()
print(heavy_calculation.cache_info())
# 输出: CacheInfo(hits=0, misses=0, maxsize=3, currsize=0)
使用场景
- 递归函数优化(如斐波那契数列)
- 数据转换/解析函数
- 配置加载等IO操作
- 计算成本高的纯函数
注意:不适合用于:
- 非确定性函数(如随机数生成)
- 有副作用的函数
- 参数不可哈希的函数(如列表、字典)
LRU Cache 在 API 中的清理操作影响范围
在 Python 的 lru_cache
中,缓存是函数级别的,清理操作只会影响调用它的特定函数实例,不会影响其他函数或模块的缓存。
关键结论:
- 每个函数有独立缓存:不同函数的缓存相互隔离
- 清理操作只影响当前函数:调用
func.cache_clear()
只清理该函数的缓存 - 同函数不同实例不共享缓存:相同函数的不同装饰器实例有独立缓存
示例演示:API 中的缓存隔离
场景:两个 API 端点使用相同计算函数但不同缓存策略
from functools import lru_cache
from fastapi import FastAPIapp = FastAPI()# 端点1:使用小型缓存
@lru_cache(maxsize=2)
def calculate_small(n: int):print(f"小型缓存计算: {n}")return n * n# 端点2:使用大型缓存
@lru_cache(maxsize=10)
def calculate_large(n: int):print(f"大型缓存计算: {n}")return n * n@app.get("/small/{n}")
async def small_endpoint(n: int):return {"result": calculate_small(n)}@app.get("/large/{n}")
async def large_endpoint(n: int):return {"result": calculate_large(n)}@app.get("/clear-small")
async def clear_small_cache():calculate_small.cache_clear()return {"message": "小型缓存已清空"}@app.get("/clear-large")
async def clear_large_cache():calculate_large.cache_clear()return {"message": "大型缓存已清空"}
测试步骤及结果:
-
首次调用小型端点
GET /small/3
→ 输出 “小型缓存计算: 3” -
再次调用相同参数
GET /small/3
→ 无计算输出(命中缓存) -
调用大型端点相同参数
GET /large/3
→ 输出 “大型缓存计算: 3”
(证明两个函数缓存独立) -
清理小型缓存
GET /clear-small
→ 返回清空消息 -
再次调用小型端点
GET /small/3
→ 重新输出 “小型缓存计算: 3”(缓存失效) -
大型端点不受影响
GET /large/3
→ 无计算输出(缓存仍然有效)
重要注意事项:
-
多进程环境:
在 Gunicorn/Uvicorn 等多进程部署中,每个工作进程有独立缓存
→ 清理操作只影响当前工作进程的缓存 -
解决方案:
# 广播清理信号给所有进程(示例) @app.get("/clear-all") async def clear_all():# 通过消息队列或共享存储通知所有进程broadcast_clear_signal()return {"message": "已发送全局清理指令"}
-
类方法缓存:
类中的不同实例共享同一个缓存(除非使用实例方法)class Calculator:@classmethod@lru_cachedef compute(cls, n): # 所有实例共享缓存return n * n
-
模块级缓存:
同一模块内的多次装饰会创建不同缓存:# module_a.py @lru_cache def func(): ... # 缓存A# module_b.py from module_a import func @lru_cache def wrapper(): # 缓存B(与func的缓存无关)return func()
最佳实践建议:
- 按需清理:只清理需要更新的函数缓存
- 添加清理端点:为关键缓存函数提供专用清理API
- 监控缓存:定期检查
cache_info()
防止内存泄漏 - 设置合理大小:避免
maxsize=None
导致无限增长 - 跨进程协调:在分布式系统中使用 Redis 等集中式缓存替代