Декораторы могут обеспечить хороший механизм для кэширования и запоминания. В качестве примера рассмотрим рекурсивное определение последовательности Фибоначчи:
def fib(num): if num < 2: return num return fib(num - 1) + fib(num - 2)
Хотя реализация проста, ее производительность во время выполнения ужасна:
>>> start = time.perf_counter(); fib(20); print('Time run:', time.perf_counter() - start) # 6765 # Time run: 0.005267535000712087 >>> start = time.perf_counter(); fib(30); print('Time run:', time.perf_counter() - start) # 832040 # Time run: 0.1682777839996561
Чтобы вычислить десятое число Фибоначчи, вам нужно вычислить только предыдущие числа Фибоначчи, но эта реализация каким-то образом требует колоссальных 177 вычислений. Ситуация быстро ухудшается для fib(20)
- 21891 вычисление и почти 2,7 миллиона вычислений для fib(30)
. Это происходит потому, что код продолжает вычислять числа Фибоначчи, которые уже известны.
Обычным решением является реализация чисел Фибоначчи с использованием цикла for ... in
и таблицы подстановки. Простое кэширование вычислений также делает свое дело:
import functools def cache(func): """Кэш предыдущих вызовов функций""" @functools.wraps(func) def wrapper(*args, **kwargs): cache_key = args + tuple(kwargs.items()) if cache_key not in wrapper.cache: wrapper.cache[cache_key] = func(*args, **kwargs) return wrapper.cache[cache_key] wrapper.cache = dict() return wrapper @cache def fib(num): if num < 2: return num return fibonacci(num - 1) + fibonacci(num - 2)
Кэш работает как таблица подстановки, поэтому теперь функция fib()
выполняет необходимые вычисления только один раз. Это сразу заметно по времени выполнения функции. Сравните с предыдущим запуском функции, без кэширующего декоратора в начале материала:
>>> import time >>> start = time.perf_counter(); fib(20); print('Time run:', time.perf_counter() - start) # 6765 # Time run: 4.560499928629724e-05 >>> start = time.perf_counter(); fib(30); print('Time run:', time.perf_counter() - start) # 832040 # Time run: 0.000410601000112365
В стандартной библиотеке доступны 2 кэширующих декоратора:
@functools.cache
модуля functools
представляет собой простой легкий неограниченный кеш функций. Иногда называется "memoization".@functools.lru_cache
кэш LRU. Этот декоратор имеет больше возможностей, чем тот, который представлен для примера. import functools, time @functools.lru_cache(maxsize=50) def fib(num): if num < 2: return num return fib(num - 1) + fib(num - 2) >>> start = time.perf_counter(); fib(30); print('Time run:', time.perf_counter() - start) # 832040 # Time run: 0.00011347100007697009 >>> fib.cache_info() # CacheInfo(hits=28, misses=31, maxsize=50, currsize=31)
Можно использовать метод fib.cache_info()
, чтобы увидеть, как работает кэш, так-же можно его настроить, если это необходимо.
В декораторе @functools.lru_cache(maxsize=50)
, параметр maxsize
указывает сколько последних вызовов кэшируется. Значение по умолчанию равно 128, но вы можете указать maxsize=None для кэширования всех вызовов функций. Однако имейте в виду, что это может вызвать проблемы с памятью.