我在一个返回一个可变对象的函数上使用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():
...
虽然没有copy
论证实际上是由functools.lru_cache
采取的。
有关如何最好地实现此行为的任何建议?
根据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]
由于lru_cache
装饰器对你来说有不合适的行为,你可以做的最好的事情是建立你自己的装饰器,它返回它从lru_cache
获得的副本。这意味着使用一组特定参数的第一次调用将创建该对象的两个副本,因为现在缓存将只保存原型对象。
这个问题变得更加困难,因为lru_cache
可以接受参数(mazsize
和typed
),因此调用lru_cache
会返回一个装饰器。记住装饰器将函数作为其参数并且(通常)返回一个函数,你必须用一个带有两个参数的函数替换lru_cache
并返回一个函数,该函数将函数作为参数并返回一个(包装)函数不是一个简单的结构包裹你的头。
然后,您将使用copying_lru_cache
装饰器而不是标准装饰器编写您的函数,现在可以在更新的装饰器内“手动”应用。
根据突变的重量程度,您可以在不使用深度复制的情况下离开,但是您没有提供足够的信息来确定它。
所以你的代码会被读取
from functools import lru_cache
from copy import deepcopy
def copying_lru_cache(maxsize=10, typed=False):
def decorator(f):
cached_func = lru_cache(maxsize=maxsize, typed=typed)(f)
def wrapper(*args, **kwargs):
return deepcopy(cached_func(*args, **kwargs))
return wrapper
return decorator
@copying_lru_cache()
def f(arg):
print(f"Called with {arg}")
x = [0, 1, arg] # Stand-in for some long computation
return x
print(f(1), f(2), f(3), f(1))
这打印
Called with 1
Called with 2
Called with 3
[0, 1, 1] [0, 1, 2] [0, 1, 3] [0, 1, 1]
所以你需要的缓存行为似乎存在。另请注意,lru_cache
的文档特别警告说
通常,只有在要重用以前计算的值时才应使用LRU高速缓存。因此,缓存具有副作用的函数,需要在每次调用时创建不同的可变对象的函数或者诸如time()或random()之类的不纯函数是没有意义的。