Кэширование Python: декоратор с таймаутом простоя

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

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

Кэш с тайм-аутом простоя

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

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

Реализуйте такой декоратор и определите, какие ответы получат терминалы при заданной последовательности запросов котировок.

Что уже дано

В редакторе представлена заготовка функции idle_cache(timeout, clock). Вспомогательные классы и проверочные функции доступны при запуске тестов, реализовывать их заново не требуется.

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

Реализуйте фабрику декоратора idle_cache(timeout, clock).

Параметры фабрики:

  • timeout — неотрицательное конечное число (int или float, включая числовые подклассы) в диапазоне от 0 до 1 000 000 секунд;
  • clock — вызываемый объект без аргументов, возвращающий текущее монотонное время в секундах (int или float от 0 до 1 000 000 000). Часы не выбрасывают исключений, а их показания не убывают.

Фабрика возвращает декоратор, принимающий синхронную целевую функцию и возвращающий функцию-обёртку.

Правила работы обёртки:

  • Время поступления текущего запроса считывается через вызов clock() в самом начале обработки, строго до возможного вызова целевой функции. Никаких пауз (sleep) и ожидания реального времени выполнять не нужно.
  • Интервал неактивности измеряется от момента поступления предыдущего любого вызова этой же обёртки (независимо от того, попал ли прошлый вызов в кэш или завершился ошибкой). При самом первом вызове предыдущего визита нет, а кэш изначально пуст.
  • Если разница current_time - last_arrival_time строго больше timeout (> timeout), весь накопленный кэш полностью очищается перед обработкой текущего запроса. Если разница меньше либо равна timeout, кэш сохраняется.
  • Время последнего визита обновляется на текущее считанное время для каждого входящего вызова.
  • Ключ кэширования формируется строго по правилу урока: (args, tuple(sorted(kwargs.items()))). Разный порядок передачи именованных аргументов формирует один и тот же ключ. Позиционные и именованные аргументы не приводятся друг к другу.
  • Если для сформированного ключа в актуальном кэше уже есть значение (попадание), обёртка возвращает сохранённый объект без вызова целевой функции.
  • Если значения в кэше нет (промах), вызывается целевая функция с исходными аргументами *args и **kwargs. При успешном завершении возвращённый объект сохраняется в кэше и возвращается вызывающему коду. Ложные значения (False, None, 0, пустые коллекции) считаются полноценными результатами и кэшируются на общих основаниях.
  • Если целевая функция выбросила исключение, оно беспрепятственно пробрасывается дальше и не сохраняется в кэше.
  • Состояние кэша и времени последнего вызова должно быть строго изолированным: декорирование нескольких разных функций одним и тем же декоратором не приводит к разделению их кэша или отметок времени.

Ввод и вывод

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

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

now=[0.0]
calls=[]
@idle_cache(5,lambda:now[0])
def quote(code):
    calls.append(code)
    return len(calls)
print(quote("EUR"))
now[0]=5
print(quote("EUR"))
now[0]=10
print(quote("EUR"))
now[0]=16
print(quote("EUR"))
print(calls)

Вывод:

1
1
1
2
['EUR', 'EUR']
РешениеPython

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

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

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