如何获得functools.lru_cache返回新实例?

问题描述 投票:0回答:2

我在一个返回一个可变对象的函数上使用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采取的。

有关如何最好地实现此行为的任何建议?

Edit

根据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]
python python-3.x caching mutable
2个回答
1
投票

由于lru_cache装饰器对你来说有不合适的行为,你可以做的最好的事情是建立你自己的装饰器,它返回它从lru_cache获得的副本。这意味着使用一组特定参数的第一次调用将创建该对象的两个副本,因为现在缓存将只保存原型对象。

这个问题变得更加困难,因为lru_cache可以接受参数(mazsizetyped),因此调用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()之类的不纯函数是没有意义的。

© www.soinside.com 2019 - 2024. All rights reserved.