Rate limiter на Python: декоратор со скользящим окном запросов
Напишите решение на Python и проверьте его на странице.
Другие упражнения по этой теме: Декораторы Python: задачи на retry, rate limiter, кэш и Circuit Breaker.
Скользящее окно запросов
Модуль взаимодействия с внешним платёжным шлюзом защищает целевую операцию от перегрузки с помощью скользящего окна вызовов. Ограничение накладывается параметризованным декоратором, который настраивается допустимым лимитом обращений, длительностью временного окна и источником монотонных часов, сохраняя метаданные функции через wraps и поддерживая для каждой декорированной функции собственную изолированную историю слотов.
В момент очередного запроса декоратор считывает текущее время и освобождает устаревшие слоты: из очереди удаляются только те временные метки, для которых разность между текущим временем и отметкой строго больше интервала окна. Если эта разность в точности равна длительности окна, метка всё ещё считается активной.
Если после проверки число занятых слотов оказывается строго меньше лимита, новое время сразу фиксируется в очереди и декоратор запускает целевую функцию. Слот остаётся занятым даже в том случае, если целевая операция завершилась ошибкой, а само возникшее исключение беспрепятственно пробрасывается дальше. Когда свободных мест нет, функция вовсе не вызывается: декоратор немедленно возбуждает RuntimeError, передавая в него точную паузу до освобождения старейшего слота — разность между моментом старейшей метки плюс интервал окна и текущим временем.
Реализуйте такой декоратор частотного ограничения вызовов для надёжного контроля сетевых запросов.
Что уже дано
В заготовке объявлена функция-фабрика window_limiter(limit, interval, clock), которую вам предстоит реализовать.
Вспомогательные классы и функции для тестирования работы декоратора доступны в тестовом окружении во время выполнения проверки; объявлять их заново в своём коде не требуется.
Что нужно сделать
Реализуйте функцию window_limiter(limit, interval, clock).
Параметры фабрики:
limit— положительное целое число от 1 до 20, задающее максимальное количество вызовов внутри скользящего окна;interval— положительное конечное число (целое или с плавающей точкой) от 0.25 до 1 000 000, определяющее длительность временного окна;clock— вызываемый объект без аргументов, возвращающий конечное неубывающее числовое время (intилиfloat).
Функция window_limiter возвращает декоратор, принимающий целевую синхронную функцию и возвращающий функцию-обёртку.
Правила работы обёртки:
- Функция-обёртка должна принимать произвольные позиционные и именованные аргументы (
*args, **kwargs) и полностью сохранять метаданные декорируемой функции: атрибуты__name__,__doc__,__annotations__,__module__,__qualname__, сигнатуру и ссылку на исходную функцию в__wrapped__(например, черезfunctools.wraps). - При повторном применении одного и того же возвращённого декоратора к нескольким разным функциям (от 1 до 3 функций) для каждой функции должна вестись строго отдельная независимая история временных меток.
- Источник времени
clock()опрашивается ровно один раз при каждом поступлении вызова обёртки (включая отклонённые вызовы). При вызове фабрикиwindow_limiterили в момент декорирования функции опрос часов производиться не должен. - Перед проверкой доступности слотов из истории удаляются только те метки, чей возраст строго больше интервала:
now - timestamp > interval. Еслиnow - timestamp == interval, слот остаётся занятым. - Если количество оставшихся активных меток строго меньше
limit, зафиксированное времяnowсразу добавляется в историю слотов, после чего вызывается целевая функция с переданными аргументами. Не фиксируйте время завершения функции, не используйтеsleepи не повторяйте вызов автоматически. - Слот остаётся занятым независимо от исхода целевой функции. Если целевая функция завершилась ошибкой, исходный объект исключения должен всплыть дальше без изменений.
- Если лимит исчерпан, целевая функция не вызывается. Обёртка немедленно выбрасывает исключение
RuntimeError(delay)(или совместимый подкласс), где единственный аргумент — числоoldest_timestamp + interval - now(включая точный0.0, если возраст старейшей метки совпал с размером окна). - Целевая функция во время исполнения может продвигать часы или рекурсивно вызывать собственную обёртку: слот текущего вызова уже должен быть зафиксирован к началу её работы.
Ввод и вывод
Стандартные потоки ввода stdin и вывода stdout в решении не используются: считывать ввод и вызывать print() не нужно. После выполнения кода учащегося проверяющая система считывает из пространства имён модуля функцию верхнего уровня window_limiter и вызывает её с проверочными аргументами.
Пример использования
now = [0.0]
def clock():
return now[0]
@window_limiter(1, 2.0, clock)
def operation(value):
return value * 2
print(operation(3))
now[0] = 2.0
try:
operation(4)
except RuntimeError as error:
print(error.args[0])
now[0] = 2.25
print(operation(4))
Вывод:
6 0.0 8
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Связанные задачи
- Functools.wraps в Python: имя функции и атрибут wrapped
- Декоратор Circuit Breaker на Python: остановка вызовов после сбоев
- Декораторы Python: проверка цепочки wrapped и метаданных функции
- Декоратор Python: логирование только медленных вызовов
- Кэширование Python: постройте ключ из выбранных аргументов функции
- Retry и fallback на Python: декоратор повторов с резервным вызовом
