Как в Python создать декоратор для асинхронной функции?

«Как в Python создать декоратор для асинхронной функции?» — вопрос из категории Асинхронность, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Чтобы создать декоратор для асинхронной (async def) функции, его внутренняя функция-обертка (wrapper) также должна быть асинхронной и использовать await для вызова декорируемой функции.

Важно использовать @functools.wraps для сохранения метаданных исходной функции (имени, документации), что упрощает отладку.

Основной шаблон:

import asyncio
import functools
import time

def async_timer_decorator(func):
    """Декоратор для измерения времени выполнения асинхронной функции."""
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        # Обертка должна быть async def
        print(f"Вызов функции {func.__name__}...")
        start_time = time.time()

        # Вызов оригинальной функции через await
        result = await func(*args, **kwargs)

        end_time = time.time()
        print(f"Функция {func.__name__} выполнилась за {end_time - start_time:.4f} секунд")
        return result
    return wrapper

@async_timer_decorator
async def some_async_task():
    print("Выполняется асинхронная задача...")
    await asyncio.sleep(1)
    return "Задача завершена"

Ключевые принципы:

  • Асинхронная обертка: Внутренняя функция wrapper объявляется с async def.
  • Использование await: Оригинальная функция вызывается с помощью await func(...).
  • Сохранение метаданных: Декоратор @functools.wraps(func) применяется к wrapper для корректного наследования атрибутов (__name__, __doc__ и т.д.).

Пример использования:

async def main():
    result = await some_async_task()
    print(result)

# Запуск в асинхронном контексте
asyncio.run(main())

# Вывод:
# Вызов функции some_async_task...
# Выполняется асинхронная задача...
# Функция some_async_task выполнилась за 1.0012 секунд
# Задача завершена