Как реализовать кэширование (мемоизацию) для функций в Python

Ответ

Мемоизация — это техника оптимизации, при которой результаты выполнения "дорогих" функций сохраняются (кэшируются), чтобы избежать повторных вычислений с теми же входными аргументами.

В Python самый простой способ реализовать мемоизацию — использовать декоратор functools.lru_cache.

Использование lru_cache

Декоратор lru_cache (Least Recently Used) кэширует результаты вызовов функции. Если функция вызывается повторно с теми же аргументами, результат возвращается из кэша, а не вычисляется заново.

Пример:

import time
from functools import lru_cache

# maxsize - максимальное количество хранимых результатов
# Если maxsize=None, кэш будет неограниченным
@lru_cache(maxsize=128)
def fibonacci(n):
    print(f"Вычисляем fibonacci({n})...")
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Первый вызов - все значения будут вычислены
print("Первый вызов:")
start = time.time()
result = fibonacci(10)
print(f"Результат: {result}, время: {time.time() - start:.6f} сек")

# Второй вызов - результат будет взят из кэша мгновенно
print("nВторой вызов:")
start = time.time()
result = fibonacci(10)
print(f"Результат: {result}, время: {time.time() - start:.6f} сек")

Важные аспекты и ограничения:

  • Хешируемые аргументы: Все аргументы функции должны быть хешируемыми (числа, строки, кортежи, frozenset). Списки и словари использовать нельзя.
  • Чистые функции: lru_cache лучше всего подходит для "чистых" функций, результат которых зависит только от входных аргументов и не имеет побочных эффектов.
  • Потокобезопасность: Кэш является потокобезопасным.

Когда lru_cache недостаточно?

lru_cache реализует in-memory кэш в рамках одного процесса. Для более сложных сценариев требуются другие решения:

  • Распределенное кэширование: Если приложение работает в нескольких процессах или на нескольких серверах, необходим внешний кэш, такой как Redis или Memcached. Это позволяет всем экземплярам приложения использовать общий кэш.
  • Персистентное кэширование: Если нужно, чтобы кэш сохранялся между перезапусками приложения, данные следует сохранять на диск, например, с помощью joblib.Memory или в базу данных.