`functools.lru_cache` —— 一行代码搞定缓存加速

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. 避坑指南

坑点 说明 解决
❌ 参数不可哈希 listdict 等可变类型作为参数会报 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
相关推荐
leeyi2 小时前
Multi-Agent:让多个 AI 分工协作完成复杂任务
后端·aigc·agent
长栎2 小时前
你的策略模式是 Map<String, Strategy>?那不过是最廉价的 if-else 替代品
后端
长栎2 小时前
你写的 abstract class 里全是钩子方法——模板模式不是让你填空,是让你别越界
后端
ping某2 小时前
语法树,到底是一棵什么形状的树?
后端
_柳青杨2 小时前
一文吃透 Node.js 事件循环:从原理到 Node 20+ 重大变更
javascript·后端
Alson_Code2 小时前
人机协作项目文档--HITL-AgentScope
后端·aigc·ai编程
IT_陈寒2 小时前
Java 并行流把我坑惨了,这6小时加班值了
前端·人工智能·后端
葫芦和十三3 小时前
图解 MongoDB 03|CRUD 全链路:一条 find 怎么穿过 WiredTiger
后端·mongodb·agent
葫芦和十三11 小时前
图解 MongoDB 04|索引模型:每建一个索引,就是在 B+-tree 森林里多栽一棵
后端·mongodb·agent