我在一个返回可变对象的函数上使用了Python的lru_cache
,像这样:
import functools
@functools.lru_cache()
def f():
x = [0, 1, 2] # Stand-in for some long computation
return x
如果我调用这个函数,改变了结果并再次调用它,我就无法获得一个“新的”,未更改的对象:
a = f()
a.append(3)
b = f()
print(a) # [0, 1, 2, 3]
print(b) # [0, 1, 2, 3]
我明白为什么会出现这种情况,但这不是我想要的。解决方法是让调用者负责使用list.copy
:
a = f().copy()
a.append(3)
b = f().copy()
print(a) # [0, 1, 2, 3]
print(b) # [0, 1, 2]
然而,我希望在
f
内部解决这个问题。一个简洁的解决方案可能是:@functools.lru_cache(copy=True)
def f():
...
虽然 functools.lru_cache
实际上不需要 copy
参数。
你有什么建议可以更好地实现这个行为吗?
编辑
基于 holdenweb 的回答,这是我的最终实现。默认情况下,它的行为与内置的 functools.lru_cache
完全相同,并在提供 copy=True
时扩展它的复制行为。
import functools
from copy import deepcopy
def lru_cache(maxsize=128, typed=False, copy=False):
if not copy:
return functools.lru_cache(maxsize, typed)
def decorator(f):
cached_func = functools.lru_cache(maxsize, typed)(f)
@functools.wraps(f)
def wrapper(*args, **kwargs):
return deepcopy(cached_func(*args, **kwargs))
return wrapper
return decorator
# Tests below
@lru_cache()
def f():
x = [0, 1, 2] # Stand-in for some long computation
return x
a = f()
a.append(3)
b = f()
print(a) # [0, 1, 2, 3]
print(b) # [0, 1, 2, 3]
@lru_cache(copy=True)
def f():
x = [0, 1, 2] # Stand-in for some long computation
return x
a = f()
a.append(3)
b = f()
print(a) # [0, 1, 2, 3]
print(b) # [0, 1, 2]
functools.lru_cache
的适当使用方式,因为您的函数没有任何参数(用于在缓存中查找先前的结果)。要使您的功能正常工作,请返回x
的副本(或deepcopy
) - 这可能会破坏在此场景中使用装饰器的目的。 - martineaulru_cache
作为functools.cache
的替代方案(记忆化,需要 py>=3.9)。它很好用,除了 Python 没有常量性(不像 C++,它是所有值语义而不是对象语义),所以缓存的对象可以被用户修改。这很麻烦。因此,在缓存对象上创建一个深拷贝是一个非常好的主意。 - dvorak4tzx