jmd*_*_dk 8 python caching mutable python-3.x
我lru_cache在返回可变对象的函数上使用 Python ,如下所示:
import functools
@functools.lru_cache()
def f():
x = [0, 1, 2] # Stand-in for some long computation
return x
Run Code Online (Sandbox Code Playgroud)
如果我调用这个函数,改变结果并再次调用它,我不会获得一个“新鲜”的、未改变的对象:
a = f()
a.append(3)
b = f()
print(a) # [0, 1, 2, 3]
print(b) # [0, 1, 2, 3]
Run Code Online (Sandbox Code Playgroud)
我明白为什么会发生这种情况,但这不是我想要的。解决方法是让调用者负责使用list.copy:
a = f().copy()
a.append(3)
b = f().copy()
print(a) # [0, 1, 2, 3]
print(b) # [0, 1, 2]
Run Code Online (Sandbox Code Playgroud)
但是我想在里面解决这个问题f。一个漂亮的解决方案是这样的
@functools.lru_cache(copy=True)
def f():
...
Run Code Online (Sandbox Code Playgroud)
尽管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]
Run Code Online (Sandbox Code Playgroud)
由于lru_cache装饰器的行为不适合您,因此您能做的最好的事情就是构建自己的装饰器,返回它从中获取的内容的副本lru_cache。这意味着使用一组特定参数的第一次调用将创建该对象的两个副本,因为现在缓存将仅保存原型对象。
这个问题变得更加困难,因为lru_cache可以接受参数(mazsize和typed),因此调用返回lru_cache一个装饰器。请记住,装饰器将函数作为其参数并(通常)返回一个函数,您必须替换lru_cache为一个接受两个参数并返回一个函数的函数,该函数接受一个函数作为参数并返回一个(包装的)函数,该函数是这不是一个容易让人理解的结构。
然后您可以使用以下方式编写您的函数copying_lru_cache装饰器而不是标准函数来编写函数,标准函数现在在更新的装饰器中“手动”应用。
根据突变的严重程度,您可能会在不使用深度复制的情况下逃脱,但您没有提供足够的信息来确定这一点。
\n\n所以你的代码会读取
\n\nfrom functools import lru_cache\nfrom copy import deepcopy\n\ndef copying_lru_cache(maxsize=10, typed=False):\n def decorator(f):\n cached_func = lru_cache(maxsize=maxsize, typed=typed)(f)\n def wrapper(*args, **kwargs):\n return deepcopy(cached_func(*args, **kwargs))\n return wrapper\n return decorator\n\n@copying_lru_cache()\ndef f(arg):\n print(f"Called with {arg}")\n x = [0, 1, arg] # Stand-in for some long computation\n return x\n\nprint(f(1), f(2), f(3), f(1))\nRun Code Online (Sandbox Code Playgroud)\n\n这打印
\n\nCalled with 1\nCalled with 2\nCalled with 3\n[0, 1, 1] [0, 1, 2] [0, 1, 3] [0, 1, 1]\nRun Code Online (Sandbox Code Playgroud)\n\n因此您需要的缓存行为似乎存在。另请注意文档lru_cache特别警告说
\n\n一般来说,仅当您想要重用以前计算的值时才应使用 LRU 缓存。因此,缓存具有副作用的函数、需要在每次调用时创建不同的可变对象的函数或不纯的函数(例如 time() 或 random())是没有意义的。
\n
| 归档时间: |
|
| 查看次数: |
1749 次 |
| 最近记录: |