Декоратор кэширования 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']
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Связанные задачи
- Functools.wraps в Python: имя функции и атрибут wrapped
- Декоратор Circuit Breaker на Python: остановка вызовов после сбоев
- Декораторы Python: проверка цепочки wrapped и метаданных функции
- Декоратор Python: логирование только медленных вызовов
- Кэширование Python: постройте ключ из выбранных аргументов функции
- Rate limiter на Python: декоратор со скользящим окном запросов
