Храните время последнего допущенного вызова по monotonic и проверяйте интервал под Lock. wraps сохраняет метаданные и исходную сигнатуру для стандартной интроспекции.
Ограничитель частоты вызова произвольной функции
Храните время последнего допущенного вызова по monotonic и проверяйте интервал под Lock. wraps сохраняет метаданные и исходную сигнатуру для стандартной интроспекции.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Создайте декоратор, который ограничивает частоту вызовов любой функции. Период ограничения (в секундах) передаётся в сам декоратор как аргумент. Если попытаться вызвать функцию до истечения указанного периода, должен быть сгенерирован исключительный тип CalledTooOftenError. Декоратор обязан корректно работать с функциями любой сигнатуры и сохранять их оригинальное объявление.
def foo():
print('foo')
Ответ
Храните время последнего допущенного вызова по monotonic и проверяйте интервал под Lock. wraps сохраняет метаданные и исходную сигнатуру для стандартной интроспекции.
from functools import wraps
from threading import Lock
from time import monotonic
class CalledTooOftenError(Exception):
pass
def rate_limit(period):
if period < 0:
raise ValueError('Период не может быть отрицательным')
def decorate(func):
guard = Lock()
last = None
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal last
with guard:
now = monotonic()
if last is not None and now - last < period:
raise CalledTooOftenError()
last = now
return func(*args, **kwargs)
return wrapper
return decorate
Интервал отсчитывается между началами допущенных вызовов, включая завершившиеся ошибкой. Блокировка не удерживается во время работы функции. Ограничитель общий для этой обёртки в одном процессе; для нескольких узлов нужно общее хранилище. Async-функциям нужен отдельный вариант, проверяющий лимит при выполнении корутины.