Python декораторы — это инструмент, который позволяют обрабатывать функцию или класс при определении. Например, добавлять поведение, регистрировать объект или заменять его другим без изменения тела исходного определения. Чтобы разобраться, что такое декоратор в Python, достаточно понять, как функции передаются между объектами и почему одна функция может возвращать другую.

Для чего нужны декораторы в Python
Основное назначение декораторов связано с принципом DRY (Don’t Repeat Yourself), то есть устранением повторяющегося кода. Если нескольким обработчикам API требуется одинаковая проверка доступа, её удобно вынести в отдельный декоратор.
Без него проверка будет выглядеть примерно так:
def get_profile(user):
if not user.is_authenticated:
raise PermissionError()
return user.profile
def get_orders(user):
if not user.is_authenticated:
raise PermissionError()
return user.ordersПри изменении правил придётся обновлять каждую функцию. Декоратор позволяет переиспользовать одну реализацию:
from functools import wraps
def login_required(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.is_authenticated:
raise PermissionError()
return func(user, *args, **kwargs)
return wrapper
@login_required
def get_profile(user):
return user.profileОбласть применения декораторов:
- логирование вызовов и обработку ошибок;
- кэширование результатов;
- проверку прав доступа;
- измерение производительности;
- регистрацию обработчиков и маршрутов.
Курс «Python Ninja» в Академии IT DMS
Сертификат, диплом о переподготовке или удостоверение о повышении квалификации.
AFFTIMES−5 % на первую покупку. Скидка суммируется с любыми акциями школы. Если оплачиваете через менеджера в чате, назовите ему промокод.
Как работают декораторы в Python
Функции как объекты первого класса
В Python функции являются объектами первого класса, их можно передавать в аргументах, присваивать переменным и возвращать из других функций. Функции относятся к вызываемым объектам — callable.
def hello():
return "Привет"
def execute(func):
return func()
result = execute(hello)Здесь execute получает функцию hello и вызывает её. Такой принцип лежит в основе декораторов.
Декоратор технически представляет вызываемый объект, который принимает декорируемый объект и возвращает результат преобразования. Чаще всего это функция, принимающая функцию и возвращающая новую функцию.
Синтаксис @ и его эквивалент
Знак @ перед определением функции обозначает применение декоратора. Следующие варианты эквивалентны:
@decorator
def func():
return 10
# Эквивалентная запись:
func = decorator(func)Python сначала создаёт функцию func, затем передаёт её декоратору. Имя func связывается с возвращённым объектом.
При обычном декорировании сам декоратор вызывается во время определения функции, а обёртка выполняется при последующих вызовах.
Простейший декоратор и замыкание
def announce(func):
def wrapper():
print("Вызов функции")
return func()
return wrapper
@announce
def hello():
return "Привет"
print(hello())wrapper получает доступ к func, объявленной во внешней функции. Такая связь называется замыканием (closure). Обёртка сохраняет ссылку на исходный объект и использует её при вызове.

Для универсального декоратора требуется поддержка аргументов:
def announce(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Вызов: {func.__name__}")
return func(*args, **kwargs)
return wrapper*args собирает позиционные аргументы, а **kwargs принимает именованные. Если их не предусмотреть, вызов метода с дополнительными параметрами может завершиться TypeError. Такая ошибка часто появляется после подключения декоратора к уже работающему коду.
Декораторы с аргументами и без
| Элемент | Без аргументов | С аргументами |
| Внешняя функция | decorator(func) | factory(option) |
| Средняя функция | wrapper(*args, **kwargs) | decorator(func) |
| Внутренняя функция | Вызывает func | wrapper(*args, **kwargs) |
| Применение | @decorator | @factory(option) |
Декоратор с аргументами
Фабрика декораторов
Когда нужно настроить поведение, используют фабрику декораторов (decorator factory). Например, можно добавить несколько повторных попыток:
def repeat(times):
def decorator(func):
@wraps(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():
print("Привет")Пример рассчитан на положительное целое times. Обёртка возвращает результат последнего вызова и прекращает работу при первом исключении.
Здесь три уровня:
- repeat(3) принимает параметр и возвращает декоратор.
- decorator(func) получает исходную функцию.
- wrapper(*args, **kwargs) выполняет её вызовы.

Сохранение метаданных через functools.wraps
Обёртка без дополнительных настроек получает собственные метаданные. Например, __name__ будет содержать wrapper, а __doc__ может оказаться равным None.
Декоратор functools.wraps помогает сохранить информацию об исходной функции:
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__}")
return func(*args, **kwargs)
return wrapperwraps переносит на обёртку основные атрибуты исходной функции, включая:
- __name__ — имя;
- __doc__ — строку документации;
- __module__ — имя модуля;
- __wrapped__ — ссылку на обёрнутый объект.
Также сохраняются аннотации и другие предусмотренные механизмом атрибуты.
Множественные декораторы
К одной функции можно применить несколько модификаторов:
@decorator_a
@decorator_b
def process():
return 1Эквивалентная запись:
process = decorator_a(decorator_b(process))

Порядок применения и выполнения различается.
- Сначала применяется нижний декоратор decorator_b.
- Его результат передаётся в decorator_a.
- При вызове управление сначала попадает во внешнюю обёртку decorator_a.
- Затем выполняется decorator_b, а после него исходная функция.
Таким образом, применение идёт снизу вверх, а вход в обёртки при вызове происходит сверху вниз.
Декораторы классов
Декоратором может выступать класс. При вызове такого декоратора создаётся экземпляр, которому передают исходную функцию.
class CallCounter:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
return self.func(*args, **kwargs)
@CallCounter
def calculate(x):
return x * 2__init__ сохраняет исходный callable, а __call__ определяет поведение экземпляра при вызове. Такой вариант подходит, когда нужно хранить состояние между обращениями.
В примере выше calculate становится экземпляром CallCounter. При декорировании методов такой объект не реализует автоматически протокол дескриптора, поэтому обычное связывание self работать не будет.
| Критерий | Декоратор-функция | Декоратор-класс |
| Реализация | Вложенная функция | __init__ и __call__ |
| Состояние | Замыкание | Атрибуты экземпляра |
| Применение | Логирование, таймер | Счётчики, состояние |
| Особенности | Компактность | Нужен учёт дескрипторов и метаданных |
Для декорирования самих классов тоже используют @. Например, @dataclass преобразует описание класса, создавая ряд методов.
Асинхронный декоратор
Для корутин нужна асинхронная обёртка:
from functools import wraps
def async_log(func):
@wraps(func)
async def wrapper(*args, **kwargs):
print("Старт")
result = await func(*args, **kwargs)
return result
return wrapperawait нужен, чтобы дождаться результата корутины. Если вызвать func(*args, **kwargs) без ожидания, получится объект корутины, а не готовое значение.

Обычная синхронная обёртка может вызвать async-функцию и вернуть корутину, но тогда код после вызова выполнится до завершения асинхронной операции. Для асинхронных функций следует применять отдельный декоратор с async def и await.
Типизация декораторов Python
Callable, TypeVar и ParamSpec
Для аннотации декораторов используют typing.Callable, TypeVar и ParamSpec. В Python 3.10 доступны ParamSpec и Concatenate.
Пример типизированного декоратора:
from collections.abc import Callable
from functools import wraps
from typing import ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def logged(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print("Вызов функции")
return func(*args, **kwargs)
return wrapperP описывает параметры исходной функции, а R обозначает тип результата. Благодаря этому анализатор типов сохраняет информацию о сигнатуре.
Callable[…, R] тоже допустим, но он не сохраняет конкретные типы параметров так точно, как ParamSpec. При этом Callable не описывает произвольную сигнатуру во всех деталях, например сложные перегрузки могут требовать дополнительных аннотаций.
Для расширения профессиональных навыков Python-разработчика изучате продвинутые возможности Python, включая работу с типами и архитектурой приложений.
Практические рекомендации
При написании собственных декораторов учитывайте несколько правил:
- Используйте @wraps(func) для сохранения метаданных.
- Передавайте *args и **kwargs, если декоратор должен работать с разными сигнатурами.
- Возвращайте результат исходной функции, если контракт не предполагает другого.
- Для async-кода используйте корректную асинхронную обёртку.
- Не храните изменяемые настройки в аргументах по умолчанию фабрики без необходимости.
Последний пункт связан с тем, что значения по умолчанию создаются один раз при определении функции. Если фабрика использует общий список или словарь, настройки разных декораторов могут неожиданно влиять друг на друга. Для независимого состояния создавайте объект внутри вызова фабрики.
Практические примеры использования декораторов
Декоратор замера времени
Для оценки продолжительности выполнения функции можно использовать time.perf_counter():
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
elapsed = time.perf_counter() - start
print(f"{func.__name__}: {elapsed:.6f} сек.")
return wrapper
@timer
def calculate():
return sum(range(1_000_000))
calculate()Замер учитывает время выполнения вызова, включая исключения. Однако он также включает ожидание внешних ресурсов, если функция блокируется на вводе-выводе.
Чтобы сравнить версии функции, можно выполнить несколько замеров без декоратора и с ним. Для честного сравнения нужны одинаковые входные данные, прогрев и серия запусков. Сам декоратор добавляет накладные расходы, поэтому для микробенчмарков лучше использовать timeit.
Кэширование через lru_cache
Стандартная библиотека functools содержит готовый декоратор @lru_cache, который сохраняет результаты вызовов:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(30))
print(fibonacci.cache_info())lru_cache (Least Recently Used) хранит результаты последних вызовов с учётом лимита maxsize. Он подходит для функций, результат которых зависит от аргументов и которые не имеют побочных эффектов.

Аргументы должны быть хешируемыми. Кэширование методов также требует учитывать self, который участвует в ключе. Если экземпляры долго живут в кэше, это может влиять на использование памяти.
Маршрутизация в веб-фреймворках
Декораторы широко используются во Flask, FastAPI и Django.
Во Flask запись @app.route(«/profile») регистрирует функцию в маршрутизаторе. Во FastAPI @app.get(«/users») связывает обработчик с HTTP-маршрутом и метаданными запроса.
В Django применяются декораторы для контроля доступа, например login_required. В CLI-инструментах Click декораторы помогают объявлять команды и параметры.
Отладка декорированных функций через inspect
При отладке сначала проверьте, какой объект привязан к имени функции:
import inspect
print(func.__name__)
print(inspect.signature(func))
print(inspect.unwrap(func))inspect.signature() обычно использует сведения __wrapped__, если они доступны. inspect.unwrap() проходит по цепочке обёрток до исходного объекта.
Если результат отличается от ожидаемого, проверьте:
- сохранил ли декоратор исходную функцию;
- используется ли @wraps;
- не изменена ли сигнатура намеренно;
- соответствует ли обёртка синхронному или асинхронному вызову.
Для трассировки исключений смотрите traceback целиком. wraps сохраняет метаданные, но не переименовывает реальные кадры выполнения. Имя wrapper всё ещё может встречаться в стеке.
FAQ
Да. Декоратор класса получает объект класса и возвращает преобразованный объект. Например, @dataclass генерирует специальные методы.
Обёртка реализует дополнительную логику вокруг вызова. Декоратор применяет такую обёртку к переданному callable и возвращает результат. Обёртка часто находится внутри декоратора.
Передавайте их при обычном вызове: func(10, name="Alex"). Внутри универсальной обёртки используйте *args и **kwargs, чтобы передать значения исходной функции.
Да. Они применяются снизу вверх. Если один декоратор зависит от результата другого, порядок может влиять на поведение. Например, кэширование и логирование могут учитывать разное количество вызовов в зависимости от расположения.
Если декоратор использует functools.wraps, исходный объект часто доступен через func.__wrapped__. Это позволяет получить оригинальную функцию, но не отменяет уже выполненные побочные эффекты и не восстанавливает автоматически состояние приложения.
Да. Каждый дополнительный уровень обёртки добавляет затраты на вызов. Для большинства прикладных задач они несущественны, но в горячих участках кода стоит измерить накладные расходы. Если декоратор выполняет сетевые запросы, запись логов или синхронизацию, их стоимость может значительно превышать стоимость самой обёртки.





