from functools import lru_cache cache = lru_cache(user_function) # или @lru_cache(maxsize=128, typed=False) def user_function(): ...
user_function
- пользовательская функция,maxsize=128
- int
, максимальный размер кеша,typed=False
- bool
, как кэшировать при разных типах аргументов.user_function
.Декоратор @lru_cache()
модуля functools
оборачивает функцию с переданными в нее аргументами и запоминает возвращаемый результат соответствующий этим аргументам. Такое поведение может сэкономить время и ресурсы, когда дорогая или связанная с вводом/выводом функция периодически вызывается с одинаковыми аргументами.
Позиционные и ключевые аргументы, переданные в функцию должны быть хешируемыми, т.к. для кэширования результатов декоратор lru_cache()
использует словарь.
Различные аргументы можно рассматривать как отдельные вызовы с отдельными записями в кэше. Например f(a=1, b=2)
и f(b=2, a=1)
различаются по порядку ключевых аргументов и могут иметь две отдельные записи в кэше.
Если указана user_function
, то она должна быть вызываемой. Это позволяет применять декоратор @lru_cache
непосредственно к пользовательской функции, оставляя значение maxsize
по умолчанию равным 128:
from functools import lru_cache @lru_cache def count_vowels(sentence): sentence = sentence.casefold() return sum(sentence.count(vowel) for vowel in 'aeiou')
Если для параметра maxsize
установлено значение None
, функция LRU декоратора отключена и кэш может расти без ограничений. Функция LRU работает лучше всего, когда maxsize
является степенью двойки.
Если для typed
задано значение True
, то аргументы функций разных типов будут кэшироваться отдельно. Например f(3)
и f(3.0)
будут рассматриваться как отдельные вызовы с разными результатами. Если введено typed=False
, ТО реализация обычно будет рассматривать их как эквивалентные вызовы и кэшировать только один результат. (Некоторые типы, такие как str
и int
, могут кэшироваться отдельно, даже если передано False
)
Обратите внимание: специфичность типа применяется только к непосредственным аргументам функции, а не к их содержимому. Скалярные аргументы Decimal(42)
и Fraction(42)
обрабатываются как отдельные вызовы с разными результатами. Напротив, аргументы кортежа ('answer', Decimal(42))
и ('answer', Fraction(42))
обрабатываются как эквивалентные.
Обернутая функция в этот декоратор будет иметь метод .cache_parameters()
(Новое в версии 3.9), которая возвращает новый dict
, показывающий значения maxsize
и typed
. Этот метод служит только для информационных целей. Изменение значений не имеет никакого эффекта.
Чтобы помочь измерить эффективность кэша и настроить параметр maxsize
, декорированная функция получает метод .cache_info()
, который возвращает именованный кортеж, показывающий hits
, misses
, maxsize
и currsize
. В многопоточной среде hits
и misses
являются приблизительными.
Декоратор также предоставляет метод .cache_clear()
для очистки или аннулирования кэша.
Исходная базовая функция доступна через атрибут __wrapped__
, что полезно для самоанализа, для обхода кеша или для преобразования функции в другой кеш.
Кэш LRU работает лучше всего, т.к. алгоритм кэширования LRU, при наличии установленного параметра maxsize
, в первую очередь вытесняет неиспользованные дольше всех кэшированные значения. Ограничение размера кеша гарантирует, что кеш не будет расти без привязки к длительным процессам, таким как веб-серверы.
В общем случае кэш LRU следует использовать только тогда, когда вы хотите повторно использовать ранее вычисленные значения. Нет смысла кэшировать функции, которые должны создавать различные изменяемые объекты при каждом вызове или нечистые функции, такие как time.time()
или random.random()
.
Пример кэша LRU для статического веб-контента:
from functools import lru_cache @lru_cache(maxsize=32) def get_pep(num): 'Retrieve text of a Python Enhancement Proposal' resource = 'http://www.python.org/dev/peps/pep-%04d/' % num try: with urllib.request.urlopen(resource) as s: return s.read() except urllib.error.HTTPError: return 'Not Found' >>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991: ... pep = get_pep(n) ... print(n, len(pep)) >>> get_pep.cache_info() # CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
Пример эффективного вычисления чисел Фибоначчи с использованием кэша для реализации техники динамического программирования:
from functools import lru_cache @lru_cache(maxsize=None) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) >>> [fib(n) for n in range(16)] # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] >>> fib.cache_info() # CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)
Изменено в версии 3.8: Добавлена опция user_function
.
Новое в версии 3.9: Добавлен метод .cache_parameters()
.