
Декораторы в Python представляют собой функции, которые принимают другую функцию и возвращают новую функцию с расширенным поведением. Создание собственного декоратора позволяет внедрять дополнительную логику без изменения исходного кода функции, что особенно полезно для логирования, проверки аргументов и кэширования результатов.
Первый шаг – определить функцию-декоратор, которая принимает функцию в качестве аргумента. Внутри неё создаётся вложенная функция, где реализуется дополнительная логика. После этого вложенная функция возвращается как результат работы декоратора. Такой подход сохраняет оригинальное поведение функции и добавляет новые возможности.
При проектировании декоратора важно учитывать передачу аргументов и возвращаемого значения. Для этого используется конструкция *args и **kwargs, позволяющая декоратору работать с функциями любого сигнатуры. Без этого декоратор будет ограничен только фиксированными параметрами.
Для отладки и сохранения метаданных исходной функции применяют модуль functools и функцию wraps. Она переносит имя функции, документацию и другие атрибуты, что важно при работе с реальными проектами и для корректного отображения информации при инспекции кода.
Следующие шаги статьи покажут, как шаг за шагом реализовать декоратор для измерения времени выполнения функций, проверки аргументов и логирования вызовов. Эти примеры демонстрируют, как универсальный декоратор может стать инструментом оптимизации и повышения прозрачности кода.
Определяем базовую функцию для декорирования

Базовая функция для декорирования должна быть простой и выполняться без побочных эффектов. Она принимает параметры, которые передаются оборачиваемой функции, и возвращает результат её выполнения. Стандартная сигнатура использует *args и **kwargs для универсальности:
def simple_function(*args, **kwargs):
def simple_function(*args, **kwargs):
result = sum(args)
return result
Для тестирования функции используйте набор разнообразных аргументов, чтобы убедиться, что она стабильно обрабатывает целые числа, строки или списки. Это важно, поскольку декоратор будет оборачивать функции с различными сигнатурами.
Рекомендуется добавлять аннотации типов для прозрачности и упрощения последующего использования функции в декораторе:
def simple_function(*args: int, **kwargs: int) -> int:
Такая подготовка позволяет создать надёжную основу для декоратора: функция легко заменяется и расширяется, а логика декорирования не ломает основной код.
Создание простой обертки с дополнительным действием

Для начала определим функцию-декоратор, которая принимает функцию как аргумент и возвращает новую функцию с дополнительным действием. Основная идея – выполнить дополнительный код до или после вызова исходной функции.
def decorator(func):
def wrapper(*args, **kwargs):
print("Начало выполнения функции")
result = func(*args, **kwargs)
return result
return wrapper
Чтобы применить декоратор, используем синтаксис @decorator перед определением функции:
@decorator
def greet(name):
print(f"Привет, {name}!")
Вызов greet("Алиса") выведет:
Начало выполнения функции
Привет, Алиса!
Рекомендуется сохранять сигнатуру и документацию исходной функции. Для этого обертку оборачивают с помощью functools.wraps:
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Начало выполнения функции")
return func(*args, **kwargs)
return wrapper
Использование wraps позволяет сохранять имя функции, аннотации и документацию, что особенно важно при работе с большими проектами и инструментами автоматической документации.
Таким образом, даже простая обертка может быть расширена дополнительным поведением, не меняя исходный код функции, и при этом оставаться прозрачной для последующего анализа или тестирования.
Передача аргументов в декоратор и обертку
Чтобы декоратор принимал собственные аргументы, необходимо создать трёхуровневую структуру функций: внешняя функция получает аргументы декоратора, средняя – функцию для обёртки, внутренняя – аргументы исходной функции.
Пример шаблона:
def decorator_with_args(deco_arg1, deco_arg2):
def actual_decorator(func):
def wrapper(*args, **kwargs):
# Логика с использованием deco_arg1, deco_arg2
result = func(*args, **kwargs)
return result
return wrapper
return actual_decorator
Аргументы декоратора обрабатываются на уровне внешней функции и доступны внутри wrapper. Это позволяет изменять поведение обёртки без изменения исходной функции.
Для наглядного понимания можно использовать таблицу распределения значений:
| Уровень | Принимаемые аргументы | Использование |
|---|---|---|
| Внешний декоратор | Аргументы декоратора | Настройка логики wrapper |
| Средняя функция | Функция, которую декорируем | Оборачивает исходную функцию |
| Внутренняя функция (wrapper) | Аргументы исходной функции | Выполняет функцию и добавляет поведение |
Важно использовать *args и **kwargs в wrapper для универсальности, чтобы декоратор корректно работал с любым количеством позиционных и именованных аргументов исходной функции.
Для передачи аргументов внутрь wrapper можно комбинировать значения декоратора и исходной функции. Например:
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
В этом примере аргумент times контролирует количество вызовов функции greet без изменения её сигнатуры.
Практическая рекомендация: всегда документируйте аргументы декоратора и сохраняйте __name__ и __doc__ функции через functools.wraps, чтобы не потерять метаданные исходной функции.
Использование functools.wraps для сохранения метаданных функции

При создании собственного декоратора часто возникает проблема потери метаданных исходной функции: имени, строки документации и атрибутов. Это влияет на работу отладчиков, систем логирования и автодокументации. Решение – использование functools.wraps.
Принцип работы functools.wraps прост: он копирует атрибуты исходной функции в обёрнутую функцию. Стандартный синтаксис выглядит так:
from functools import wraps
def мой_декоратор(func):
@wraps(func)
def обёртка(*args, **kwargs):
# дополнительная логика
return func(*args, **kwargs)
return обёртка
Без @wraps(func) атрибуты функции заменяются атрибутами внутренней обёртки:
__name__→ «обёртка»__doc__→ строка документации декоратора- Специальные атрибуты, такие как
__annotations__и__module__, теряются
Рекомендации по использованию:
- Импортируйте
wrapsтолько изfunctools, других реализаций нет. - Применяйте
@wrapsнепосредственно к внутренней функции обёртки, перед её определением логики. - Если декоратор принимает параметры,
wrapsприменяется к функции, возвращаемой вложенным декоратором. - Для сложных случаев с несколькими уровнями обёртки используйте
wrapsна каждом уровне, чтобы сохранить цепочку метаданных.
Пример с параметрами декоратора:
def декоратор_с_параметром(prefix):
def декоратор(func):
@wraps(func)
def обёртка(*args, **kwargs):
print(f"{prefix}: вызов функции")
return func(*args, **kwargs)
return обёртка
return декоратор
@декоратор_с_параметром("INFO")
def функция_пример():
"""Документация функции"""
return 42
В результате:
функция_пример.__name__→ «функция_пример»функция_пример.__doc__→ «Документация функции»
Использование functools.wraps обеспечивает прозрачность декораторов и предотвращает потерю критически важных метаданных.
Применение декоратора к методам классов

Простейший пример декоратора для метода:
def log_call(func):
def wrapper(self, *args, **kwargs):
print(f"Вызов метода {func.__name__} с аргументами {args} {kwargs}")
return func(self, *args, **kwargs)
return wrapper
class Example:
@log_call
def greet(self, name):
return f"Привет, {name}!"
Здесь wrapper принимает self первым аргументом. Без этого метод не сможет корректно работать с экземпляром класса.
Для классовых методов (@classmethod) декоратор должен принимать cls:
def class_logger(func):
def wrapper(cls, *args, **kwargs):
print(f"Классовый метод {func.__name__} вызван с {args} {kwargs}")
return func(cls, *args, **kwargs)
return wrapper
class Example:
@classmethod
@class_logger
def info(cls, data):
return f"Информация: {data}"
Важно соблюдать порядок декораторов: сначала пользовательский декоратор, затем @classmethod, либо использовать functools.wraps, чтобы сохранить метаданные функции.
Для методов, которые должны быть статическими (@staticmethod), декоратор не получает автоматически self или cls. В этом случае wrapper должен принимать только те аргументы, которые реально используются.
Использование декораторов с методами классов позволяет централизованно добавлять логирование, проверку прав, кэширование или профилирование без изменения основного кода метода.
Комбинирование нескольких декораторов на одной функции

В Python можно накладывать несколько декораторов на одну функцию, размещая их в виде стека сверху вниз. Вызов выполняется в обратном порядке: сначала применяется внутренний декоратор, затем внешний. Например, если функция `func` обернута как `@d1 @d2 def func(): …`, сначала выполняется `d2(func)`, затем результат передается в `d1`.
При комбинировании важно учитывать передачу аргументов и возвращаемых значений. Каждый декоратор должен корректно обрабатывать `*args` и `**kwargs`, иначе внутренняя функция может получить некорректные данные. Для сохранения метаданных исходной функции используют `functools.wraps`, иначе `__name__`, `__doc__` и аннотации будут утеряны.
Порядок декораторов критичен при выполнении логики, зависящей от побочных эффектов. Например, если один декоратор логирует вызов, а другой кеширует результат, правильное расположение определяет, будет ли лог записан до или после кеширования.
Для сложных сценариев можно комбинировать декораторы с аргументами. Например, `@auth(role=’admin’) @timer def func(): …` сначала создаст обертку с таймером, затем добавит проверку роли. Важно, чтобы декораторы с параметрами возвращали функцию-обертку, иначе цепочка сломается.
Рекомендуется тестировать комбинированные декораторы отдельно, чтобы убедиться, что каждый сохраняет корректное поведение и не нарушает работу других. Использование модульного подхода облегчает отладку и повышает предсказуемость вызова функции.
Вопрос-ответ:
Что такое декоратор в Python и зачем он нужен?
Декоратор — это функция, которая принимает другую функцию и возвращает новую функцию с изменённым или расширенным поведением. Он позволяет добавлять дополнительный функционал к существующему коду без изменения самой функции. Например, можно логировать вызовы функций, проверять аргументы или измерять время выполнения.
Как создать простой декоратор для вывода сообщения перед и после вызова функции?
Для этого нужно определить функцию-декоратор, которая принимает функцию как аргумент и возвращает новую функцию. Внутри новой функции добавляются действия до и после вызова исходной функции. Например, внутри вложенной функции print можно вставить строки «Начало» и «Конец», а потом вызвать исходную функцию с переданными аргументами.
В чем разница между декоратором с аргументами и без аргументов?
Декоратор без аргументов просто принимает функцию и возвращает изменённую версию. Декоратор с аргументами требует дополнительного уровня вложенности: сначала создаётся функция, принимающая аргументы декоратора, которая возвращает сам декоратор, а уже тот работает с целевой функцией. Это позволяет настраивать поведение декоратора динамически.
Как сохраняется информация о исходной функции после применения декоратора?
Если декоратор не использовать модуль functools.wraps, метаданные исходной функции, такие как __name__ и __doc__, заменяются на данные вложенной функции. Для сохранения этих атрибутов нужно обернуть внутреннюю функцию в wraps, импортированный из functools. Это полезно для документации и отладки.
Можно ли применять несколько декораторов к одной функции и как это делается?
Да, несколько декораторов можно накладывать друг на друга. Они применяются сверху вниз по записи. Например, если функция декорируется сначала @декоратор1, потом @декоратор2, то вызов функции сначала пройдет через декоратор2, а затем через декоратор1. Это позволяет комбинировать разные виды логики без изменения исходного кода функции.
Как правильно передавать аргументы в собственный декоратор в Python?
Чтобы декоратор корректно работал с функциями, принимающими аргументы, нужно использовать вложенную функцию с параметрами *args и **kwargs. Это позволяет обернутой функции принимать любое количество позиционных и именованных аргументов без ошибок. Сначала создаётся внешний декоратор, который принимает функцию, затем внутри него определяется обёртка, которая обрабатывает аргументы и выполняет дополнительные действия до или после вызова исходной функции. После этого декоратор возвращает обёртку вместо оригинальной функции. Такой подход обеспечивает гибкость и сохраняет возможность использовать декоратор с различными функциями.
