1. 知识点简介
缓存是优化重复计算最朴素有效的手段。Python 标准库 functools.lru_cache 提供 最近最少使用(LRU)缓存 装饰器,只需要加一行 @lru_cache,就能让纯函数自动缓存结果,大幅提速。Python 3.9+ 还新增了更简洁的 @cache(等效于 @lru_cache(maxsize=None))。
核心价值:
- 减少重复计算,尤其适合递归、动态规划、频繁调用的纯函数
- 内置 LRU 淘汰策略,内存可控
- 透明使用,函数签名和类型注解不受影响
- 自带的
cache_info()可以查看命中率,辅助调优
2. 核心用法
python
from functools import lru_cache, cache
基础装饰器:
python
@lru_cache(maxsize=128) # 最多缓存 128 个不同参数的结果
def expensive_func(n: int) -> int:
print(f"⏳ 正在计算 f({n})")
return n * n
简易版(Python 3.9+):
python
@cache # 等效于 @lru_cache(maxsize=None),不限制大小
def fib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
3. 实战示例
3.1 斐波那契数列:无缓存 vs 有缓存
python
from functools import lru_cache
import time
# 无缓存版本
def fib_naive(n: int) -> int:
if n < 2:
return n
return fib_naive(n - 1) + fib_naive(n - 2)
# 有缓存版本
@lru_cache(maxsize=256)
def fib_cached(n: int) -> int:
if n < 2:
return n
return fib_cached(n - 1) + fib_cached(n - 2)
# 对比
n = 35
start = time.perf_counter()
fib_naive(n)
print(f"无缓存耗时: {time.perf_counter() - start:.4f}s")
start = time.perf_counter()
fib_cached(n)
print(f"有缓存耗时: {time.perf_counter() - start:.4f}s")
# 查看缓存统计
print(fib_cached.cache_info()) # CacheInfo(hits=32, misses=36, maxsize=256, currsize=36)
输出差异巨大:n=35 时,无缓存约 3-5s ,有缓存约 0.0001s,加速数千倍。
3.2 爬虫请求去重(实战场景)
python
import requests
from functools import lru_cache
@lru_cache(maxsize=200)
def fetch_page(url: str) -> str:
"""缓存已抓取的页面内容,避免重复请求"""
print(f"🌐 请求: {url}")
resp = requests.get(url, timeout=10)
return resp.text
# 第一次调用 -> 发真实请求
html1 = fetch_page("https://httpbin.org/get")
# 第二次调用 -> 直接返回缓存,不发送请求
html2 = fetch_page("https://httpbin.org/get")
print(fetch_page.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=200, currsize=1)
⚠️ 注意:lru_cache 的参数必须是 可哈希(hashable) 的。URL 是字符串,天然可哈希;但如果是 dict / list 类型参数,会报错。
3.3 清空与调优
python
# 清空所有缓存
expensive_func.cache_clear()
# 查看缓存统计
info = expensive_func.cache_info()
print(f"命中: {info.hits}, 未命中: {info.misses}, 当前大小: {info.currsize}, 最大: {info.maxsize}")
# 调整缓存大小(Python 3.8+)
expensive_func.cache_parameters() # {'maxsize': 128}
4. 避坑指南
| 坑点 | 说明 | 解决 |
|---|---|---|
| ❌ 参数不可哈希 | list、dict 等可变类型作为参数会报 TypeError |
转成 tuple / frozenset,或用自定义 hash |
| ❌ 函数有副作用 | 缓存跳过计算,副作用也只执行一次 | 只在纯函数(无副作用)上使用 |
| ❌ 内存泄漏 | maxsize=None 缓存无限增长 |
线上环境务必设置 maxsize,或配合 cache_clear() |
| ❌ 跨进程无效 | 每个进程独立缓存,多进程场景不共享 | 改用 Redis / Memcached 等外部缓存 |
| ❌ 默认参数导致误判 | func(a=1) 和 func(1) 在 lru_cache 中算不同参数 |
调用时保持一致风格 |
5. 扩展:@cache vs @lru_cache(maxsize=None)
Python 3.9 引入了 @functools.cache:
python
from functools import cache
@cache
def get_data(key: str) -> list:
...
两者的区别仅在于 cache 没有限制最大缓存数量,且内部实现略快一点点。适合缓存规模可控的场景(如已知参数集合有限)。不确定时,默认用 @lru_cache(maxsize=128) 更安全。
6. 总结
@lru_cache是 Python 标准库中最被低估的利器之一- 一行装饰器 = 完整 LRU 缓存实现,适合纯函数加速
- 动态规划、递归、API 请求去重、配置文件解析等场景特别适用
- 牢记避坑点:纯函数、可哈希参数、生产环境限 maxsize