Декораторы, асинхронные функции и контекстные менеджеры в Python
В этой статье разберём несколько ключевых возможностей Python: создание декораторов разных видов, работу с асинхронными функциями, кеширование результатов и написание собственных контекстных менеджеров.
Декораторы позволяют изменить или расширить поведение функции без изменения её исходного кода. По сути, это функция, принимающая другую функцию и возвращающая новую, «обёрнутую» версию.
Простой декоратор
Наиболее базовый пример — декоратор без параметров. Он принимает функцию и возвращает новую функцию, которая вызывает оригинальную.
@deco
def my_func():
pass
my_func = deco(my_func)
from typing import Callable
def deco(func: Callable):
def wrapper():
res = func()
return res
return wrapper
В этом примере wrapper вызывает func и возвращает её результат. Запись с @deco полностью эквивалентна ручному присваиванию my_func = deco(my_func).
Функция с параметром
Если исходная функция принимает аргументы, то и обёртка должна уметь их обрабатывать. В этом случае используются *args и **kwargs, чтобы поддерживать произвольный набор параметров.
@deco
def my_func(arg_func):
pass
my_func = deco(my_func)(arg_func)
from typing import Callable
def deco(func: Callable):
def wrapper(arg_func): # *args, **kwargs
res = func(arg_func) # *args, **kwargs
return res
return wrapper
Обратите внимание: декоратор остаётся прежним, но теперь его внутренняя функция принимает параметры и передаёт их оригинальной функции.
Декоратор с параметрами
Иногда самого декоратора недостаточно, и требуется передавать аргументы в сам декоратор. Тогда структура становится вложенной: функция-декоратор возвращает функцию-обёртку, которая уже принимает декорируемую функцию.
Также важно сохранять метаданные функций (__name__, __doc__). Для этого используется functools.wraps.
@deco(arg_deco)
def my_func(arg_func):
pass
my_func = deco(arg_deco)(my_func)(arg_func)
from typing import Callable
from functools import wraps
def deco(arg_deco):
def wrapper(func: Callable):
@wraps(func)
def inner(arg_func): # *args, **kwargs
res = func(arg_func) # *args, **kwargs
return res
return inner
return wrapper
Такая структура подходит, например, когда декоратору нужны настройки, передаваемые пользователем.
Декораторы для асинхронных функций
Асинхронные функции (async def) требуют специальной обработки — их необходимо вызывать с помощью await. Поэтому для таких случаев создаются асинхронные обёртки.
@deco
async def my_func():
return 1
my_func = deco(my_func)
from typing import Coroutine
def deco(coroutine: Coroutine):
async def wrapper(*args, **kwargs):
ret = await coroutine(*args, **kwargs)
return ret
return wrapper
Декоратор превращается в асинхронный (async def wrapper), что позволяет корректно «ожидать» выполнение исходной корутины.
Кэширование результата функции
Python предоставляет удобный механизм кэширования — functools.lru_cache. Он позволяет сохранять результаты вызовов функций и повторно использовать их без повторных вычислений.
from functools import lru_cache # least-recently-used
@lru_cache
def my_long_calc():
time.sleep(5)
return 42
# все 3 функции выполнятся одновременно
print(my_long_calc())
print(my_long_calc())
print(my_long_calc())
В данном примере фактически длительная функция вызывается только один раз — последующие вызовы используют кэш.
Создание своего контекстного менеджера
Контекстные менеджеры используются для автоматического выполнения действий при входе и выходе из блока with. Они могут управлять ресурсами, логировать события и выполнять завершающие операции.
Python предоставляет удобный декоратор @contextmanager, позволяющий писать контекстные менеджеры без создания отдельного класса.
from contextlib import contextmanager
@contextmanager
def ctx_manager():
print('hello')
yield
print('end')
with ctx_manager() as man:
print('print')
Функция, помеченная @contextmanager, разделяется оператором yield на две части:
- код до
yieldвыполняется при входе в блокwith, - код после
yieldвыполняется при выходе.
Заключение
Декораторы и контекстные менеджеры — мощные инструменты Python, позволяющие делать код компактным, гибким и выразительным. Декораторы помогают изменять поведение функций, не вмешиваясь в их тело, а контекстные менеджеры обеспечивают корректное и безопасное управление ресурсами.
Эти механизмы часто используются в веб-разработке, асинхронном программировании, логировании, тестировании и многих других сферах, поэтому их понимание существенно упрощает написание чистого и эффективного кода.