Логотип YeaHub

База вопросов

Собеседования

Тренажёр

База ресурсов

Обучение

Навыки

Войти

Выбери, каким будет IT завтра — вместе c нами!

YeaHub — это полностью открытый проект, призванный объединить и улучшить IT-сферу. Наш исходный код доступен для просмотра на GitHub. Дизайн проекта также открыт для ознакомления в Figma.

© 2026 YeaHub

Документы

Медиа

Назад
Вопрос про Python: python, decorator, async, asyncio, wrapper function

Как сделать декоратор, который принимает и синхронную, и асинхронную функцию?

Этот вопрос проверяет умение создавать универсальные декораторы в Python, которые могут работать как с обычными, так и с асинхронными функциями, что важно для написания гибкого и переиспользуемого кода.

Короткий ответ

Чтобы создать декоратор, работающий и с синхронными, и с асинхронными функциями, нужно внутри него определить обёртку, которая проверит, является ли результат вызова исходной функции корутиной (т.е. асинхронным объектом). Если да, то обёртка сама должна быть асинхронной и использовать await. В противном случае она возвращает обычный результат. Это позволяет единообразно применять логику декоратора (например, логирование, замер времени) к разным типам функций.

Длинный ответ

В Python декоратор — это функция, которая принимает другую функцию и возвращает новую, обычно добавляя какую-то дополнительную логию. С появлением асинхронного программирования (async/await) часто возникает необходимость, чтобы один и тот же декоратор мог работать как с обычными синхронными функциями, так и с асинхронными. Ключевая идея заключается в том, чтобы внутри обёртки (wrapper) определить, что вернул вызов исходной функции: обычное значение или корутину (объект типа coroutine).

Основной подход

Создадим функцию-декоратор universal_decorator. Внутри неё определим обёртку. При вызове обёртки мы сначала выполняем целевую функцию (с переданными аргументами) и получаем результат. Затем проверяем, является ли этот результат "awaitable" (например, с помощью inspect.iscoroutine()). Если да, то нам нужно вернуть асинхронную обёртку, которая будет ждать этот результат. Поэтому сама обёртка тоже должна быть асинхронной функцией. Но как сделать одну обёртку и для синхронного, и для асинхронного случая? Часто используют такой приём: создают две разные внутренние функции или делают саму обёртку асинхронной, но это может быть неэффективно для синхронных вызовов. Более элегантный способ — определить обёртку, которая проверяет тип результата и соответственно себя ведёт.

Пример реализации

Вот практический пример декоратора, который замеряет время выполнения функции и работает с обоими типами функций:

import asyncio
import time
from functools import wraps
from inspect import iscoroutinefunction

def timer_decorator(func):
    @wraps(func)
    def sync_wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"{func.__name__} took {elapsed:.4f} seconds (sync)")
        return result

    @wraps(func)
    async def async_wrapper(*args, **kwargs):
        start = time.time()
        result = await func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"{func.__name__} took {elapsed:.4f} seconds (async)")
        return result

    if iscoroutinefunction(func):
        return async_wrapper
    else:
        return sync_wrapper

# Пример использования
@timer_decorator
def sync_func():
    time.sleep(1)
    return "done sync"

@timer_decorator
async def async_func():
    await asyncio.sleep(1)
    return "done async"

# Синхронный вызов
print(sync_func())  # Выведет время и "done sync"
# Асинхронный вызов
print(asyncio.run(async_func()))  # Выведет время и "done async"

В этом примере мы используем inspect.iscoroutinefunction() для проверки, является ли декорируемая функция асинхронной. В зависимости от этого возвращаем либо синхронную, либо асинхронную обёртку. Это эффективно, потому что для синхронной функции не создаются лишние асинхронные конструкции.

Где это применяется

Такой подход полезен в библиотеках и фреймворках, где декораторы предоставляют общую функциональность (логирование, кэширование, проверка прав доступа, повторные попытки) и должны работать прозрачно как в синхронном, так и в асинхронном контексте. Например, в веб-фреймворках типа FastAPI или aiohttp могут быть декораторы маршрутов, которые обрабатывают и синхронные, и асинхронные обработчики.

Вывод: Универсальные декораторы, поддерживающие оба типа функций, стоит применять, когда вы пишете библиотечный код или инструменты, которые будут использоваться в смешанной синхронно-асинхронной среде, чтобы избежать дублирования логики и повысить переиспользуемость.

Уровень

  • Рейтинг:

    3

  • Сложность:

    6

Навыки

  • Python

    Python

Ключевые слова

#python

#decorator

#async

#asyncio

#wrapper function

Подпишись на Python Developer в телеграм