You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 07:18:12