Как заставить functools.lru_cache возвращать новые экземпляры? - PullRequest
0 голосов
/ 27 февраля 2019

Я использую 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. Любое предложение о том, как лучше всего реализовать это поведение?

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]

1 Ответ

0 голосов
/ 27 февраля 2019

Поскольку декоратор lru_cache не подходит для вас, лучшее, что вы можете сделать, - это создать собственный декоратор, который возвращает копию того, что он получает от lru_cache.Это будет означать, что первый вызов с определенным набором аргументов создаст две копии объекта, поскольку теперь в кеше будут храниться только объекты-прототипы.

Этот вопрос усложняется, поскольку lru_cache может занятьаргументы (mazsize и typed), поэтому вызов to 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 ().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...