Что должен уметь декоратор для измерения времени выполнения функции с порогом оповещения?
Что проверяют
Умеете ли вы написать декоратор с параметром, а это три уровня вложенности, и знаете ли, чем корректно измерять длительность. Дальше смотрят на детали, по которым видно, писали ли вы такое в бою: метаданные обёртки, поведение при исключении, куда уходит оповещение.
Как отвечать
Соберите требования списком, потом покажите код:
- порог приходит параметром, значит внешняя функция берёт
threshold, средняя саму функцию, внутренняя выполняет вызов; - время меряется через
time.perf_counter, он монотонный и с высоким разрешением.time.timeэто настенные часы, их может подвинуть синхронизация; - замер стоит в
try/finally: упавший вызов тоже должен попасть в отчёт, потому что медленное часто заканчивается таймаутом; - аргументы прокидываются через
*args, **kwargs, результат возвращается без изменений; functools.wrapsобязателен;- при превышении порога пишем в
loggingс именем функции и фактическим временем, а не вprint.
def timed(threshold):
def deco(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
spent = time.perf_counter() - start
if spent > threshold:
logging.warning("%s: %.3f s", func.__name__, spent)
return wrapper
return decoГде спотыкаются
Забывают functools.wraps. Формально работает, но все обёрнутые функции превращаются в wrapper: ломается интроспекция, автодокументация и веб-фреймворки, которые регистрируют обработчики по имени функции и падают на конфликте имён.
Оборачивают генератор или корутину и получают ноль. Тело генератора не исполняется до первой итерации, а async def возвращает объект корутины сразу, поэтому замер закрывается раньше, чем начнётся работа. Для асинхронного случая нужна отдельная обёртка с await внутри.
Глотают исключение ради красивого отчёта. except без повторного возбуждения превращает падение в тихий None, и ошибка всплывает в другом месте. Декоратор наблюдает за функцией, а не меняет её поведение.