Включите исполнение JavaScript в браузере, чтобы запустить приложение.
13 янв 2025

Кэширование функций в Python

Кэширование в контексте Python — это процесс, подразумевающий сохранение результатов вызовов функции в памяти для повышения производительности путем их повторного использования. В этой статье рассмотрим, зачем оно нужно, как использовать встроенный в Python декоратор functools.lru_cache и внешние хранилища.

Зачем нужно кэширование 

Перечислим основные задачи кэширования:

  • увеличение скорости выполнения программ. Иногда программы состоят из функций, которые выполняются многократно или достаточно долго. Например, если они обрабатывают большой объем данных. Сохранение результатов этих вычислений в кэше обеспечивает быстрый доступ к ним и устраняет необходимость повторных вызовов, за счет чего программа выполняется быстрее;
  • сокращение нагрузки на внешние источники информации. Чтобы получить необходимую информацию, приложения делают запросы к базам данных, API и другим источникам, а когда такие запросы многократно повторяются, производительность снижается. Кэширование позволяет приложениям быстро извлекать нужную информацию из кэша вместо постоянного обращения к внешним системам;
  • улучшение пользовательского опыта. Здесь в первую очередь речь идет о веб-приложениях, которые должны реагировать на запросы пользователей с низкой задержкой. Если сохранять ранее запрашиваемую информацию в кэше, то приложение сможет получить к ней доступ быстрее, за счет чего опыт взаимодействия пользователей улучшится.

Встроенный декоратор

В Python существует встроенный инструмент для кэширования — декоратор functools.lru_cache. Помимо своей основной задачи, он автоматически удаляет из кэша те значения, которые не использовались дольше всего (аббревиатура LRU в названии расшифровывается как Least Recently Used), если кэш заполнен. lru_cache() не подходит для хранения больших объемов данных и использования в распределенных системах.

У lru_cache() есть два параметра:

  • maxsize (значение по умолчанию 128) — определяет максимальный размер кэша, то есть результаты скольких вызовов функций будут сохранены. Когда кэш достигнет указанного размера, значения, которые давно использовались, начнут удаляться. Если установить maxsize=None, то все результаты будут сохраняться до тех пор, пока не закончится память — этим можно пользоваться, но стоит помнить, что риск снижения производительности в таком случае повышается;
  • typed (значение по умолчанию False) — если установлено значение True, то результаты вызова функции с аргументами разных типов (например, f(3) и f(3.0)) будут сохраняться как отдельные записи. Если установлено значение False, то результаты вызова функции с аргументами разных типов будут сохранены в одну запись.

Небольшой пример использования декоратора:

from functools import lru_cache

@lru_cache(maxsize=32)  # Максимальный размер кэша — 32, по умолчанию typed=False

def multiply_by_two(value):

    return value * 2

multiply_by_two(5)  # Будет записано в кэш

multiply_by_two(5.0)  # Вернется значение из кэша, так как typed=False
py

Если изменить значение typed на True, то указанные вызовы функции будут сохранены в разные записи кэша.

Рассмотрим несколько полезных методов.

  • cache_info() для просмотра статистики кэша:
some_function.cache_info() 
py
  • cache_clear() для очистки кэша:
some_function.cache_clear() 
py
  • cache_parameters() для просмотра параметров (maxsize и typed):
some_function.cache_parametres() 
py

Кэширование с использованием внешних хранилищ

Внешние хранилища позволяют обойти ограничения встроенного в Python-декоратора: они подходят для использования в распределенных системах и могут хранить большие объемы данных. 

Рассмотрим два популярных инструмента:

  • Redis — это NoSQL СУБД, которая хранит данные в формате «ключ-значение» и в том числе используется для кэширования. Установить Redis можно через стандартный для Python менеджер пакетов pip:
pip install redis
py

Пример использования Redis:

import redis

# Подключение

cache = redis.Redis(host='localhost', port=6379, db=0)

def multiply_by_two(value):

    # get() используется для получения данных из кэша

    cached_result = cache.get(value)

    # Проверка value в кэше 

    if cached_result:

        return int(cached_result)

    # Если значения в кэше нет, то оно будет сохранено

    else:

        result = value * 2

        # Первый аргумент setex — это ключ, по которому значение можно получить/удалить

        # Второй — время, на которое сохраняется значение в секундах

        # Третий — значение, которое сохраняется

        cache.setex(value, 60, result)  

        return result

# Синтаксис для удаления одного ключа

cache.delete(key)
py
  • Memcached — это похожий на Redis инструмент, который тоже хранит информацию в формате «ключ-значение». Однако скорее подходит для сохранения промежуточных данных или другой информации, которую не нужно хранить долго. Установить Memcached можно следующим образом:
pip install pymemcache
py

Пример использования Memcached схож с Redis:

from pymemcache.client import base

# Подключение

cache = base.Client(('localhost', 11211))

def multiply_by_two(value):

    cached_result = cache.get(value)

    if cached_result:

        return int(cached_result)

    else:

        result = value * 2

        cache.set(value, result, expire=60)

        return result
py

Заключение

  • Смысл кэширования сводится к сохранению результатов вызовов сложных или многократно используемых функций для быстрого доступа к ним и снижения нагрузки на систему.
  • В Python есть встроенный декоратор functools.lru_cache, однако он не подходит для хранения большого количества значений и использования в распределенных системах.
  • Внешние хранилища, такие как Redis и Memcached, позволяют разработчикам обойти ограничения lru_cache.