Store the last admitted call time and compare it with time.monotonic() under a lock. Reject early calls with CalledTooOftenError; functools.wraps preserves metadata and exposes the original signature to introspection.
Throttle execution frequency of an arbitrary function
Store the last admitted call time and compare it with time.monotonic() under a lock. Reject early calls with CalledTooOftenError; functools.wraps preserves metadata and exposes the original signature to introspection.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Создайте декоратор, который ограничивает частоту вызова заданной вызываемой функции. Временное окно (в секундах) передается в декоратор в качестве аргумента. Если обернутая функция вызывается снова до истечения указанного интервала, декоратор должен поднять исключение CalledTooOftenError. Декоратор должен быть универсальным, работать с любыми вызываемыми объектами и сохранять сигнатуру исходной функции.
def sample_func():
print('foo')
Ответ
Store the last admitted call time and compare it with time.monotonic() under a lock. Reject early calls with CalledTooOftenError; functools.wraps preserves metadata and exposes the original signature to introspection.
This implementation limits call admission, counts failed calls, and does not hold the lock while the wrapped callable runs.
from functools import wraps
from math import isfinite
from threading import Lock
from time import monotonic
class CalledTooOftenError(Exception):
pass
def throttle(interval):
if not isfinite(interval) or interval < 0:
raise ValueError('Expected a finite nonnegative interval')
def decorate(func):
last = None
lock = Lock()
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal last
with lock:
now = monotonic()
if last is not None and now - last < interval:
raise CalledTooOftenError()
last = now
return func(*args, **kwargs)
return wrapper
return decorate
Functions and callable instances can be wrapped. The Python wrapper accepts *args/**kwargs; inspect.signature normally follows wrapped, rather than the runtime signature being regenerated. For async callables this limits coroutine creation, not execution: use an async wrapper if execution-time throttling is required.