#python #function #caching #memoization #functools
#python #функция #кэширование #запоминание #функциональные средства
Вопрос:
Как вы можете настроить lru_cache
, чтобы использовать его кэш на основе фактических полученных значений, а не того, как была вызвана функция?
>>> from functools import lru_cache
>>> @lru_cache
... def f(x=2):
... print("reticulating splines...")
... return x ** 2
...
>>> f()
reticulating splines...
4
>>> f(2)
reticulating splines...
4
>>> f(x=2)
reticulating splines...
4
Другими словами, только первый вызов выше должен быть промахом кэша, два других должны быть попаданиями в кэш.
Комментарии:
1. В документах указано: отдельные шаблоны аргументов могут рассматриваться как отдельные вызовы с отдельными записями в кэше. Например, f(a=1, b=2) и f(b=2, a=1) различаются по порядку аргументов ключевых слов и могут иметь две отдельные записи кэша . Похоже, вы можете использовать этот
f.cache_info()
метод, чтобы увидеть фактические попадания / промахи в кэш.2. Я знаю, но я не думаю, что это разумное поведение по умолчанию — вызовы для всех практических целей одинаковы. Я хотел бы обернуть (или заменить) lru_cache таким образом, чтобы избежать промахов кэша для всех различных вариантов написания одного и того же базового вызова.
3. @bnaecker: Нет, потому что поведение, которого пытается достичь вопрос, зависит от сигнатуры функции, которая
make_key
не знает.4. Почему
f()
иf(x=2)
не обрабатываются одинаково? Не такargs=()
ли иkwds={'x': 2}
в обоих случаях?5. @mkrieger1: Нет. Значения по умолчанию не являются аргументами ключевого слова.
Ответ №1:
Для этого вам нужно будет пройти процесс привязки аргументов к формальным параметрам. Фактический процесс выполнения этого реализован в коде C без открытого интерфейса, но в нем есть (гораздо более медленная) повторная inspect
реализация. Это примерно в 100 раз медленнее, чем functools.lru_cache
при обычном использовании:
import functools
import inspect
def mycache(f=None, /, **kwargs):
def inner(f):
sig = inspect.signature(f)
f = functools.lru_cache(**kwargs)(f)
@functools.wraps(f)
def wrapper(*args, **kwargs):
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
return f(*bound.args, **bound.kwargs)
return wrapper
if f:
return inner(f)
return inner
@mycache
def f(x):
print("reticulating splines...")
return x ** 2
Если снижение производительности при таком подходе слишком велико, вы можете вместо этого использовать следующий трюк, который требует большего дублирования кода, но выполняется намного быстрее, всего в 2 раза медленнее, чем lru_cache
при обычном использовании (а иногда и быстрее, с аргументами ключевых слов):
@functools.lru_cache
def _f(x):
print("reticulating splines...")
return x ** 2
def f(x=2):
return _f(x)
Это использует гораздо более быструю привязку аргументов уровня C для нормализации вызова записанной вспомогательной функции, но требует дублирования параметров функции 3 раза: один раз в сигнатуре внешней функции, один раз в сигнатуре помощника и один раз при вызове помощника.
Комментарии:
1. Отлично работает. Как твой C? Есть ли интерес собрать CPython PR, добавив аргумент «нормализовать»
lru_cache
напрямую?