当前位置: 首页 > news >正文

Python functools.lru_cache 实战:一行加缓存、可变参数坑与手动失效

Python functools.lru_cache 实战:一行加缓存、可变参数坑与手动失效

有个函数算得慢,或者要反复调远程接口,你想「同样的入参别重复算」。手写一个 dict 当缓存?几行就能踩三个坑:线程安全、缓存无上限撑爆内存、失效逻辑一堆。其实标准库的functools.lru_cache一行装饰器就搞定了大半——但它也有几个不看文档必踩的坑。这篇讲清楚怎么用、什么时候会翻车、怎么手动控制。

从手写缓存的痛点说起

先看没缓存的递归斐波那契,指数级重复计算:

deffib(n):ifn<2:returnnreturnfib(n-1)+fib(n-2)# fib(35) 要算好几秒,因为同一个 fib(k) 被重复算了成千上万次

手写缓存版:

_cache={}deffib(n):ifnin_cache:return_cache[n]result=nifn<2elsefib(n-1)+fib(n-2)_cache[n]=resultreturnresult

能用,但问题一堆:_cache是个全局变量污染命名空间、无上限会一直涨、多线程下in判断和写入之间有竞态、想清空还得手动_cache.clear()。这些lru_cache都替你处理好了。

一行搞定

fromfunctoolsimportlru_cache@lru_cache(maxsize=None)# None 表示不限容量deffib(n):ifn<2:returnnreturnfib(n-1)+fib(n-2)print(fib(100))# 瞬间出结果,每个 fib(k) 只算一次

lru_cache帮你做了:自动用参数当 key 缓存返回值、LRU(最近最少使用)淘汰、线程安全的读写。名字里的 LRU 指的是——当缓存条数超过maxsize,自动淘汰最久没被访问的那条。

maxsize怎么选:

  • 参数取值有限、想全缓存(如斐波那契、配置解析):maxsize=None
  • 参数空间大、怕内存涨:给个上限如maxsize=1024,超了自动淘汰旧的。
  • Python 3.9+ 如果就是想「无限缓存」,直接用@cache更语义化(等价于lru_cache(maxsize=None))。

坑一:参数必须可哈希,list/dict 直接报错

lru_cache拿参数当字典的 key,所以参数必须可哈希(hashable)。传 list、dict、set 会当场炸:

@lru_cachedefprocess(items):returnsum(items)process([1,2,3])# TypeError: unhashable type: 'list'

解法是把可变参数换成不可变的:

@lru_cachedefprocess(items):# items 现在期望是 tuplereturnsum(items)process((1,2,3))# 传 tuple,OK

如果调用方手上是 list,在调用前转一下:process(tuple(my_list))。要缓存「基于 dict 配置」的函数,可以把 dict 转成排序后的tuple(sorted(d.items()))再传。

坑二:关键字参数和位置参数算不同的 key

lru_cache区分「参数是位置传的还是关键字传的」。同样的逻辑入参,写法不同会被当成两次不同调用,各缓存一份:

@lru_cachedefadd(a,b):print(f"计算{a}+{b}")returna+b add(1,2)# 打印"计算 1+2",算一次add(1,2)# 命中缓存,不打印add(a=1,b=2)# 又打印"计算 1+2"!因为 key 和位置传参不同

后果是缓存命中率下降、缓存里存了重复内容。实战建议:对要缓存的函数,团队约定统一调用风格(要么都位置传、要么都关键字传),或者在函数签名里用/把参数限制为仅位置参数,从源头消除歧义。

坑三:别缓存「有副作用」或「结果会变」的函数

lru_cache的前提是纯函数——同样的入参永远返回同样的结果,且没有副作用。违背这个前提就会出诡异 bug:

@lru_cachedefget_user_config(user_id):# 危险:数据库里的配置会变,但缓存永远返回第一次读到的值returndb.query("SELECT * FROM config WHERE user_id=?",user_id)

用户在数据库里改了配置,你的函数还在返回旧值,而且你可能查半天都找不到原因。规则:结果会随时间/外部状态变化的函数,不要无脑套 lru_cache;要缓存也得配一个明确的失效策略。

同理,别缓存返回可变对象的函数——调用方拿到缓存的 list 后改了它,会污染缓存里的那份:

@lru_cachedefget_defaults():return["a","b"]# 返回的是同一个 list 对象d=get_defaults()d.append("c")# 改的是缓存里那个 list!print(get_defaults())# ['a', 'b', 'c'] —— 被污染了

要么返回不可变的 tuple,要么在调用处 copy 一份。

手动查缓存状态与失效

lru_cache装饰后的函数带两个实用方法:

@lru_cache(maxsize=128)defslow(n):returnn*n slow(2);slow(3);slow(2)# 查命中情况:hits=1 misses=2 maxsize=128 currsize=2print(slow.cache_info())# 手动清空整个缓存(比如配置变更后)slow.cache_clear()print(slow.cache_info())# 清空后 currsize=0

cache_info()hits/misses能帮你评估缓存到底有没有用——如果 hits 长期接近 0,说明这个函数根本没有重复调用,加缓存纯属浪费内存,该撤掉。

cache_clear()是唯一的失效手段,但它是全量清空,没法只删某一个 key。如果你需要「按 key 精细失效」,lru_cache就不够用了,得换成手写 dict 或专门的缓存库(如cachetools,它支持 TTL 过期和单 key 删除)。

一个务实的选择清单

  • 纯函数、想加缓存、参数可哈希 →@cache/@lru_cache(maxsize=N),一行搞定。
  • 需要过期时间(TTL)lru_cache不支持,用cachetools.TTLCache
  • 需要按 key 失效lru_cache只能全清,用cachetools或自己管 dict。
  • 结果会变 / 有副作用 → 别缓存,或想清楚失效策略再缓存。

小结

  • lru_cache一行给纯函数加线程安全的 LRU 缓存,maxsize=None(或 3.9+ 的@cache)不限容量,给数字就自动淘汰旧条目。
  • 三个坑:参数必须可哈希(list/dict 要转 tuple)、位置传参和关键字传参算不同 key(统一调用风格)、别缓存结果会变或返回可变对象的函数。
  • cache_info()看命中率判断缓存值不值得,cache_clear()全量失效——需要 TTL 或按 key 失效就上cachetools

记忆点:lru_cache只配得上「同样输入永远同样输出、参数可哈希」的纯函数;越界的场景,换缓存库或手写。

http://www.jsqmd.com/news/1242327/

相关文章:

  • 【Python课程设计/毕业设计】基于 Python Web 的医院门诊预约系统 高校 / 社区医院预约挂号服务系统设计 智能门诊预约与医生排班管理系统【附源码、数据库、万字文档】
  • 问卷调查设计:从基础到高级的数据收集技巧
  • 数据库变更如何做到向前兼容与安全回退
  • 北京华恒智信赋能航天国企:岗位价值+能力系数薪酬改革
  • 2026厦门黄金回收亲测筛选指南|5家正规回收门店横向对比 - 商业每日快报
  • andrej-karpathy-skills项目:为AI编程助手制定行为准则的工程化解决方案
  • 为什么选择aws2tf?1612种Terraform AWS资源全覆盖的终极工具
  • 记录:适配360安全浏览器v10版本
  • 计算机毕业设计之学习助力平台的设计与实现
  • TI Tiva微控制器EPI接口配置:通用模式与主机总线模式详解
  • 无锡手表回收渠道横向测评,合扬实体连锁交易更安心,热门款式保值盘点 - 好物测评局
  • PXE+Kickstart自动化安装RHEL7.9系统
  • 铁沟浇注料如何选型降本?自流式与低水泥结合性能实测对比
  • SphereFace PyTorch:如何用深度学习实现99.22%的人脸识别准确率?
  • 计算机毕业设计之装修公司管理系统
  • 视频多路复用技术:原理、模式与TI VIP解析器实战指南
  • 2026苏州全域包包回收|专业鉴定,轻微磨损瑕疵正常回收 - 逸程奢侈品回收中心
  • 香港科技大学校园体验日----工学院理学硕士MSc课程专场
  • 2026年最新教程:怎么把视频做成GIF 亲测有效的方法 - 图片处理研究员
  • HarmonyOS应用开发实战:萌宠日记 - 照片日期分组展示
  • 【9】数学形态学操作(上)
  • Tiva™ CAN驱动开发实战:中断处理与位定时配置详解
  • 嵌入式通信接口寄存器配置实战:以太网PHY与USB控制器详解
  • 小程序毕设项目:基于SpringBoot的移动端博物馆客流预约管控服务平台 博物馆门票预订与参观评价管理系统 (源码+文档,讲解、调试运行,定制等)
  • ros安装教程
  • 深入解析KeyStone II系统追踪:多核SoC调试与性能剖析实战
  • 校园旧书共享交易平台的设计与实现
  • 5分钟打造你的专属代码编辑器:GriddyCode终极定制指南
  • 如何快速上手INAV Configurator?新手必备的5分钟安装与连接教程
  • Tiva TM4C129 EPI接口配置详解:从原理到SDRAM与SRAM实战