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

如何让Python functools.lru_cache的返回值变为不可变?

解决方案

functools.lru_cache缓存的是函数返回对象的引用,直接修改引用指向的可变对象(如list、dict)时,缓存内存储的对象也会被同步修改,导致后续调用拿到错误结果。你可以通过以下方案把缓存返回值转为不可变,触发非法修改时直接抛错,快速定位问题代码:

方案1:基础不可变转换装饰器(适用于平层返回值)

通过装饰器把常用可变类型转为对应不可变类型,修改时直接触发TypeError:

import functools
from types import MappingProxyType

def freeze_return_value(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        # 对常用可变类型做不可变转换,可根据业务需求扩展规则
        if isinstance(result, list):
            return tuple(result)
        elif isinstance(result, dict):
            return MappingProxyType(result)
        elif isinstance(result, set):
            return frozenset(result)
        return result
    return wrapper

使用时把该装饰器放在lru_cache下方(靠近函数定义,保证先冻结结果再存入缓存):

@functools.lru_cache(maxsize=None)
@freeze_return_value
def your_cached_func(*args):
    # 原有业务逻辑
    return [1, 2, 3, {"key": "value"}]

方案2:递归深冻结(适用于嵌套可变结构)

如果返回值是嵌套的可变结构,用递归逻辑做深度冻结:

from typing import Any

def deep_freeze(obj: Any) -> Any:
    if isinstance(obj, list):
        return tuple(deep_freeze(item) for item in obj)
    elif isinstance(obj, dict):
        return MappingProxyType({k: deep_freeze(v) for k, v in obj.items()})
    elif isinstance(obj, set):
        return frozenset(deep_freeze(item) for item in obj)
    # 自定义可变类型可在这里补充冻结逻辑
    return obj

# 替换基础装饰器里的转换逻辑即可
def freeze_return_value(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return deep_freeze(func(*args, **kwargs))
    return wrapper

方案3:返回深拷贝(仅避免缓存污染,不触发报错)

如果不需要定位修改位置,仅保证缓存结果不被篡改,可以给返回值做深拷贝,修改拷贝对象不会影响缓存内的原始值:

import functools
import copy

def copy_return_value(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return copy.deepcopy(result)
    return wrapper

注意该方案的装饰器要放在lru_cache上方(远离函数定义,保证每次从缓存拿到结果后先拷贝再返回给调用方):

@copy_return_value
@functools.lru_cache(maxsize=None)
def your_cached_func(*args):
    return [1,2,3]

注意事项

  • 装饰器顺序不能错,冻结逻辑要先执行再存缓存,拷贝逻辑要先取缓存再执行拷贝
  • 自定义类返回值如果要冻结,可以用dataclasses.dataclass(frozen=True)定义返回对象,或者重写类的__setattr__方法修改时抛错
  • Python 3.10+ 中可以直接用functools.cache替代functools.lru_cache(maxsize=None),以上方案完全通用

内容的提问来源于stack exchange,提问作者linrongbin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 22:36:03