使用functools.cache时,如何传递预计算列表且不缓存该列表?
问题:使用functools.cache时,传递预计算列表且避免缓存该列表的最优方案
我在库中写了一个依赖大量预计算输入子集的函数:
from functools import cache @cache def f(n, precompute): if n < len(precompute): return precompute[n] # 递归计算,返回结果会被缓存 return some_compute(f(n-1, precompute))
现在需要解决的问题是:用functools.cache给这个函数做记忆化时,怎么以最用户友好的方式传递precompute列表,同时不让precompute被缓存?
我自己梳理了几个思路:
- 将
precompute设为全局/外部作用域变量(缺点是函数不纯,全局变量需要预先在模块中定义) - 将
precompute设为函数属性,以此作为绕过缓存的“参数”(因为函数是对象,这个方案可行) - 编写仅基于第一个参数的自定义记忆化装饰器
- 将其转换为类,让预计算值成为实例属性或类属性(sympy.sieve就采用了类似方案)
- 将
precompute的值加载到cache(f)中(效率低,因为缓存是字典,而precompute是千万级规模的列表) - 创建仅含一个参数的内部辅助递归函数,在外部作用域中使用
precompute(我觉得多次外部调用时不可行,因为内部函数会超出作用域)
最优方案分析
方案1:改进版内部辅助函数(推荐,用户友好且简洁)
你担心内部函数多次调用不可行是误解——可以把外部函数作为入口,每次调用外部函数时创建绑定当前precompute的内部递归函数,同时给内部函数加缓存。这样外部函数负责接收precompute,内部函数只处理n参数,缓存不会包含precompute:
from functools import cache def f(n, precompute): @cache def inner_f(k): if k < len(precompute): return precompute[k] return some_compute(inner_f(k-1)) return inner_f(n)
每次调用f(n, new_precompute)时,都会生成一个新的inner_f及其独立缓存,完全隔离不同的precompute,同时用户调用方式和原来一致,非常友好。唯一需要注意的是,如果频繁调用不同的precompute,会生成多个缓存实例,但这是合理的——不同预计算集下的结果本来就应该分开缓存。
方案2:函数属性方式(适合单预计算集场景)
如果你的库大部分时候只用一套固定的precompute,偶尔需要切换,可以把precompute设为函数的属性:
from functools import cache def f(n): if n < len(f.precompute): return f.precompute[n] return some_compute(f(n-1)) # 初始化预计算集 f.precompute = []
使用时先给f.precompute赋值,再调用f(n)。优点是缓存只基于n,效率高;缺点是函数不纯,多线程/多场景切换预计算集时需要注意同步。
方案3:类封装(适合复杂场景)
如果需要管理多套预计算集,或者预计算集需要动态更新,类封装是最规范的方式,类似sympy.sieve的实现:
from functools import cache class ComputeWithPrecompute: def __init__(self, precompute): self.precompute = precompute # 把缓存绑定到实例,不同实例的缓存隔离 self._inner_f = cache(self._inner_f) def _inner_f(self, k): if k < len(self.precompute): return self.precompute[k] return some_compute(self._inner_f(k-1)) def __call__(self, n): return self._inner_f(n) # 使用方式 calc = ComputeWithPrecompute(my_precompute_list) result = calc(1000)
这种方式最清晰,每个实例对应一套预计算集和独立缓存,适合需要长期维护多套计算上下文的场景,用户调用也直观。
不推荐的方案
- 全局变量:破坏函数纯性,不利于测试和多场景复用
- 自定义装饰器:需要额外编写装饰器逻辑,不如内部函数简洁,且增加维护成本
- 直接加载precompute到cache:千万级规模的列表会导致缓存爆炸,内存占用极高,完全不可行
内容的提问来源于stack exchange,提问作者qwr
相关产品推荐
相关产品推荐

