配置文件已经改了,lru_cache 为何仍返回旧值:缓存键与失效边界

举报
yd_294604443 发表于 2026/11/07 09:08:27 2026/11/07
【摘要】 给读取配置的函数加上 lru_cache,可以减少重复调用,却不会自动感知文件内容变化。本文用 Python 标准库的本地合成实验,说明缓存键、容量淘汰和主动清空分别影响什么,再给出把缓存移到纯解析步骤的可运行例子。

摘要:给读取配置的函数加上 lru_cache,可以减少重复调用,却不会自动感知文件内容变化。本文用 Python 标准库的本地合成实验,说明缓存键、容量淘汰和主动清空分别影响什么,再给出把缓存移到纯解析步骤的可运行例子。

一个程序首次从 limit.txt 读到数字2,随后文件改成5,再调用同一个函数,结果却仍是2。排查时如果只确认“文件确实保存了”,容易把问题归到路径或编码。真正需要先看的,是这次调用有没有重新执行读取。

路径相同,不代表路径指向的内容相同

本次先定义一个由 @lru_cache(maxsize=2) 装饰的函数,函数体用 Path.read_text() 读取文件,再用 int() 解析。调用参数只有 Path 对象,没有文件内容或版本信息。

缓存首先根据调用参数查找已有结果。命中时,直接返回保存的结果,函数体不会再次执行。因此,文件在磁盘上从2变成5,并不会自动改变这个路径参数对应的缓存键。可哈希的参数只满足建立缓存键的条件,不保证函数结果永远只由参数决定。

本次新建 a.txt 与 b.txt,内容分别为2和7,按固定顺序执行以下操作:

操作 返回值 实际读取文件次数,累计
依次读取 a、b 2、7 2
将 a 改为5,再读取 a 2 2
清空缓存,再依次读取 a、b 5、7 4

第二步后,另行读取磁盘确认 a 的文本已是 5\n;被缓存的函数却仍返回2。此时 cache_info() 显示 hits=1、misses=2、currsize=2,和函数体只执行两次的计数相符。这是主动构造的本地反例,没有据此宣称某个实际系统存在故障。

maxsize 限制条目数量,不负责过期

maxsize=2 表示最多保留两个缓存条目,没有“保存两秒”的含义。LRU 依据最近使用情况淘汰条目,不依据文件何时被改写判断结果是否有效。

另一组实验依次请求 A、B、A、C、B,缓存函数把大写字母转为小写,同时记录实际执行。结果只执行了 A、B、C、B 四次:第二次访问 A 命中,使 B 成为较久未用的条目;加入 C 后 B 被淘汰,最后访问 B 就需要重算。最终 hits=1、misses=4、currsize=2。

这组结果说明容量淘汰的顺序,不能转写成“旧数据会很快自动刷新”。一个持续被访问的旧结果,仍可能不断命中。若业务需要有效期,应另行定义计时、到期后的加载和失败处理规则;本文没有实现或验证带有效期的缓存。

主动清空也有明确范围

第一组调用 read_limit.cache_clear() 后,hits、misses 和 currsize 都变为0。随后 a、b 均重新读取,说明清空影响的是这个包装函数保存的全部条目,并非只让发生变化的 a 失效。

对于由本进程统一管理的配置更新,更新后主动清空可以是一种选择,但需要明确由谁负责调用。该方法不会通知其他进程的独立缓存;也不能把一次清空解释为多个线程、多个文件更新时的统一版本切换。本次实验全部顺序执行,没有验证这些并发情况。

把变化的读取留在缓存外面

如果想复用的是同一份文本的解析结果,可以每次读取文件,只缓存文本到值的转换。下面的完整例子只处理自行生成的小型整数文本,返回不可变的 int:

from functools import lru_cache
from pathlib import Path
from tempfile import TemporaryDirectory


@lru_cache(maxsize=2)
def parse_limit(text):
    return int(text)


def load_limit(path):
    return parse_limit(Path(path).read_text(encoding="utf-8"))


with TemporaryDirectory() as folder:
    path = Path(folder) / "limit.txt"
    for text in ("2\n", "5\n", "2\n"):
        path.write_text(text, encoding="utf-8")
        print(load_limit(path))
    print(parse_limit.cache_info())

本次输出依次为2、5、2,最后一行为 CacheInfo(hits=1, misses=2, maxsize=2, currsize=2)。独立计数的同结构实验确认:读取文件三次,解析函数执行两次。第三份文本与第一份相同,因此可以复用已有的解析结果。

这里改变的是缓存位置。读取函数仍然每次访问磁盘;解析函数的输入则包含本次实际读到的文本,相同文本得到相同整数,不再暗中依赖某个路径当时的内容。如果解析还依赖语言、规则版本等条件,也必须把这些依赖纳入设计,不能只搬动装饰器就假定已经解决。

缓存原始文本也会占用空间,两个条目不等于两个字节。面对较大或外部来源的文件,仍需另外限制读取大小、校验数值范围及处理读取异常。本例只用极短文本解释行为,没有测速度;对一次 int 转换,缓存本身的管理成本可能并不划算,不能由“少解析一次”推导出性能提升。

每次读取也只代表取得了这一次读取返回的内容。它没有让读写文件成为一个原子事务,更没有保证配置发布期间总能读到完整的新版本。缓存失效和文件更新的一致性,应分别定义和验证。

2026年11月7日,本实验在 Windows、Python 3.12.14 下实际运行三组观察,另执行文中完整代码。两个子进程各运行一次,均退出0,stderr均为空,原始 stdout、stderr 字节、调用计数、退出码及 SHA256 清单已本地保存。全部输入是新建的合成文本,未调用网络、云服务或生产系统,也未测并发与性能。判断缓存是否合适,先写清哪些输入决定结果,再决定何时允许复用。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。