Как считать время выполнения функции в декораторе, если функция упала с ошибкой
Что проверяют
Знаете ли вы, как try/finally ведёт себя при исключении, и не сломает ли ваш декоратор программу, проглотив ошибку. Следом обычно просят сделать то же для async-функции или отправлять время в метрики с пометкой, успешным ли был вызов.
Как отвечать
Засекаете время до вызова, вызываете функцию внутри try, а замер кладёте в finally. Он выполнится и при возврате, и при исключении, а исключение после него полетит дальше.
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
status = "error"
try:
result = func(*args, **kwargs)
status = "ok"
return result
finally:
elapsed = time.perf_counter()-start
log.info("%s %s %.4f", func.__qualname__, status, elapsed)
return wrapperЧто проговорить:
time.perf_counter(): монотонные часы высокого разрешения.time.time()берёт системное время, которое сдвигается при синхронизации;functools.wraps: сохраняет имя, докстринг и__wrapped__, иначе в логах будетwrapper;- статус: флаг меняется только после успешного возврата, так в логе видно, упала ли функция;
- корутины: обычная обёртка замерит лишь создание корутины. Нужна отдельная
async def-обёртка сawait, выбирать её можно поinspect.iscoroutinefunction(func).
Где спотыкаются
Ловят except Exception, печатают ошибку и возвращают None. Вызывающий код получает мусор вместо исключения, и баг всплывает в другом месте.
Пишут return внутри finally. Он молча отменяет летящее исключение, и декоратор снова глотает ошибки.
Берут time.time() и считают замер точным. Сдвиг системных часов во время вызова даёт отрицательное или раздутое время.