47. Асинхронный декоратор медленных вызовов
Условие здаадчи:
Дан асинхронный код. Нужно реализовать декоратор slowlog(threshold=3), который измеряет время выполнения асинхронной функции.
Требования:
если функция выполняется быстрее или ровно за threshold секунд, логирование не выполняется если функция выполняется дольше threshold секунд, нужно что-то залогировать декоратор должен корректно работать с async def функциями возвращаемое значение исходной функции должно сохраняться без изменений
import asyncio
@slowlog(threshold=3)
async def some_func(seconds):
await asyncio.sleep(seconds)
return seconds * 2
assert asyncio.run(some_func(1)) == 2 # не должно логгироваться
assert asyncio.run(some_func(5)) == 10 # должно логгироваться
# После превышения threshold секунд, мы логгируем что-то
Спойлеры к решению
Подсказки
- Декоратор должен принимать параметр
threshold, значит нужен декоратор-фабрика. - Так как декорируемая функция асинхронная, обёртка тоже должна быть
async def. - Внутри обёртки нужно вызвать исходную функцию через
await. - Для измерения времени лучше использовать
time.perf_counter(). - Если время выполнения больше
threshold, нужно выполнить логирование. - Возвращаемое значение исходной функции нужно вернуть без изменений.
- Чтобы сохранить имя и метаданные функции, стоит использовать
functools.wraps.
Решение
import asyncio
import time
from functools import wraps
def slowlog(threshold: float = 3):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = await func(*args, **kwargs)
end_time = time.perf_counter()
duration = end_time - start_time
if duration > threshold:
print(
f"Slow function: {func.__name__} "
f"worked {duration:.2f} seconds"
)
return result
return wrapper
return decorator
Пример использования:
@slowlog(threshold=3)
async def some_func(seconds):
await asyncio.sleep(seconds)
return seconds * 2
assert asyncio.run(some_func(1)) == 2 # не логгируется
assert asyncio.run(some_func(5)) == 10 # логгируется
Примерный вывод для второго вызова:
Slow function: some_func worked 5.00 seconds
Декоратор slowlog(threshold=3) сначала возвращает обычный декоратор decorator, а тот уже принимает исходную функцию func.
Так как some_func является асинхронной функцией, внутри wrapper обязательно используется:
result = await func(*args, **kwargs)
Без await функция не была бы выполнена нормально, а вместо результата вернулась бы coroutine.
Проверка времени выполняется после завершения функции:
if duration > threshold:
print(...)
По условию логирование должно быть только если функция работает дольше threshold. Поэтому при duration == threshold логирования не будет.
Сложность по времени самого декоратора — O(1), если не учитывать время работы исходной функции. По памяти — O(1).