我在返回可变对象的函数上使用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]
答案 0 :(得分:1)
由于lru_cache
装饰器对您而言不合适,因此,您最好的办法就是构建自己的装饰器,该装饰器返回从lru_cache
获得的内容的副本。这意味着第一次调用带有一组特定的参数将创建该对象的两个副本,因为现在缓存将仅保存原型对象。
这个问题变得更加困难,因为lru_cache
可以接受参数(mazsize
和typed
),因此对lru_cache
的 call 返回一个装饰器。记住装饰器将一个函数作为其参数,并且(通常)返回一个函数,您必须将lru_cache
替换为一个带有两个参数的函数,并返回一个将一个函数作为参数并返回(包裹)功能,这不是一个容易缠住你的头的结构。
然后,您将使用copying_lru_cache
装饰器来编写函数,而不是现在在更新的装饰器中“手动”应用的标准装饰器。
根据突变的严重程度,您可能无需使用Deepcopy就能逃脱,但是您没有提供足够的信息来确定突变。
这样您的代码便会读取
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())都是没有意义的。
答案 1 :(得分:0)
我想要:
import functools
@functools.lru_cache()
def inner_f():
x = [0, 1, 2] # Stand-in for some long computation
return x
def f():
return inner_f().copy()
因此,lru_cache进行计算,包装函数进行创建。