Декоратор кэширования Python: проверка свежести результата

Напишите решение на Python и проверьте его на странице.

Другие упражнения по этой теме: Декораторы Python: задачи на retry, rate limiter, кэш и Circuit Breaker.

Кэш векторных слоёв с проверкой свежести

Сервис построения графиков кэширует готовые векторные слои, чтобы не перерисовывать их заново при повторяющихся запросах. Результаты вычислений сохраняются в словаре замыкания и привязываются к конкретному набору параметров отрисовки. Однако исходные данные периодически обновляются, поэтому перед повторным использованием сохранённого слоя нужно проверять, не потерял ли он актуальность.

Эту проверку выполняет параметризованный декоратор, принимающий внешнюю функцию-предикат. При каждом вызове предикат оценивает текущий кортеж аргументов и возвращает булево значение. Если предикат возвращает истину, данные для этого набора параметров считаются устаревшими: декоратор находит старую запись в кэше и удаляет её прямо перед дальнейшей обработкой. На сохранённые результаты для других параметров эта очистка не влияет.

Если после проверки предиката в кэше остаётся действующее значение, декоратор сразу возвращает его без запуска целевой функции. Если же записи нет или её только что удалили из-за устаревания, функция вызывается заново, а полученный векторный слой заносится в словарь кэша и передаётся вызывающему коду.

Реализуйте такой декоратор кэширования с выборочной инвалидацией записей по предикату свежести аргументов.

Что уже дано

В редакторе представлена заготовка функции freshness_cache(is_stale) с заглушкой raise NotImplementedError.

При выполнении решения также доступна вспомогательная утилита observe_freshness_cache, используемая тестами. Реализовывать её самостоятельно не требуется.

Что нужно сделать

Реализуйте функцию-фабрику freshness_cache(is_stale). Она принимает предикат is_stale и возвращает декоратор, который оборачивает синхронную функцию func.

Обертка принимает произвольные аргументы *args и **kwargs со значениями хэшируемых типов (включая числа, строки, кортежи и None) и работает по следующим правилам:

  • Для каждого вызова формируется составной ключ вида (args, tuple(sorted(kwargs.items()))). Позиционные аргументы сохраняются в кортеже args, а именованные преобразуются в отсортированный по именам кортеж пар (имя, значение). Совпадение запросов определяется стандартным равенством и хэшированием кортежей Python.
  • Кэш хранится в словаре и изолирован для каждой декорированной функции.
  • Перед каждым обращением к кэшу (включая потенциальные попадания) вызывается is_stale(key), возвращающий булево значение.
  • Если предикат вернул True и запись для текущего ключа уже существует в кэше, она удаляется до проверки наличия или вызова целевой функции. Записи для других ключей остаются нетронутыми.
  • Если предикат вернул False, существующая запись для текущего ключа сохраняется.
  • При попадании в кэш обертка возвращает оригинальный сохранённый объект без повторного вызова целевой функции.
  • При промахе целевая функция func вызывается со всеми исходными аргументами без изменений, её результат сохраняется в кэше и возвращается. В кэш могут сохраняться любые объекты, включая изменяемые структуры и значения, приводящиеся к False.
  • Удаление устаревшей записи строго предшествует выполнению функции: если целевая функция внутри себя выполняет вложенный повторный вызов с тем же ключом после сброса предиката в False, старого устаревшего объекта в кэше уже быть не должно.
  • Дополнительных механизмов TTL, вытеснения по объёму, фонового обновления или обработки сбоев реализовывать не нужно. Количество вызовов в тестах составляет от 0 до 1000, а число именованных аргументов — от 0 до 20.

Ввод и вывод

Стандартные потоки ввода stdin и вывода stdout в решении не используются: считывать ввод и вызывать print() не нужно. После выполнения кода учащегося проверяющая система считывает из пространства имён модуля функцию верхнего уровня freshness_cache и вызывает её с проверочными аргументами.

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

stale_keys = set()
calls = []
@freshness_cache(lambda key: key in stale_keys)
def layer(name):
    calls.append(name)
    return {'name': name}
first = layer('roads')
print(layer('roads') is first)
stale_keys.add((('roads',), ()))
print(layer('roads') is first)
stale_keys.clear()
print(calls)

Вывод:

True
False
['roads', 'roads']
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи