Что такое декораторы в Python и зачем они нужны
Декораторы в Python — это как швейцарский нож для программиста: универсальный инструмент, который новички обходят стороной (потому что «выглядит сложно»), а опытные разработчики используют везде, где только можно. И неспроста.

По сути, декоратор позволяет «обернуть» одну функцию в другую, добавив ей новые возможности без изменения исходного кода. Звучит абстрактно? Представьте, что у вас есть обычная функция, которая что-то вычисляет. А теперь вы хотите логировать каждый её вызов, или проверять права доступа пользователя, или измерять время выполнения. Без декораторов пришлось бы переписывать function или копировать однотипный код везде.
Декораторы решают эту проблему элегантно: один раз написали логику, применили к любой функции через символ @ — и готово. Поэтому их активно используют во Flask (для маршрутизации), Django (для проверки прав), pytest (для фикстур) и вообще везде, где нужно добавить «общее поведение» к разным функциям.
- Основы: как работают функции в Python
- Что такое замыкание (closure)
- Что такое декоратор в Python
- Примеры простых декораторов
- Продвинутые возможности декораторов
- Применение нескольких декораторов
- Параметризованные декораторы (фабрики)
- Классы как декораторы
- Где применяются декораторы на практике
- Ошибки, которых стоит избегать
- Заключение
- Рекомендуем посмотреть курсы по Python
Основы: как работают функции в Python
Прежде чем нырнуть в декораторы с головой, нужно понять одну фундаментальную особенность Python: здесь function — это не просто блоки кода, а полноценные объекты. Да-да, как строки, числа или списки. Это называется «объекты первого класса» (first-class objects), и именно эта особенность делает возможными все фокусы с декораторами.
Что это означает на практике? А то, что с функциями можно делать всё то же самое, что и с любыми другими объектами:
- Их можно передавать как аргументы — отдать одну другой и сказать «вот, поработай с этой».
- Можно возвращать из других function — функция может создать новую внутри себя и вернуть её как результат.
- Можно хранить как переменную — присвоить function переменной, положить в список, в словарь, куда угодно.
Именно поэтому в Python возможны функции высшего порядка (higher-order functions) — те, что принимают или возвращают другие function. Декораторы — это частный случай таких функций, только очень полезный и красиво оформленный синтаксически.
Без этого понимания декораторы действительно кажутся магией. С ним — логичным развитием языковых возможностей.
Что такое замыкание (closure)
Теперь добираемся до концепции, которая лежит в основе декораторов — замыканий. Замыкание — это функция, которая «запомнила» своё окружение и может использовать переменные из области видимости, где она была создана, даже после того, как эта область «исчезла».
Звучит как научная фантастика? На деле это довольно практичная штука. Представьте: function завершила работу, все её локальные переменные должны были исчезнуть, но внутренняя функция каким-то образом их «захватила» и продолжает с ними работать.
Как работает область видимости (LEGB)
Чтобы понять замыкания, нужно разобраться с тем, как Python ищет переменные. Есть правило LEGB — четыре уровня области видимости, которые Python проверяет по порядку:
Уровень | Описание | Пример |
---|---|---|
L (Local) | Локальные переменные внутри функции | def func(): x = 1 |
E (Enclosing) | Переменные из внешней (охватывающей) функции | Переменные внешней функции, доступные вложенной |
G (Global) | Глобальные переменные модуля | x = 1 на уровне модуля |
B (Built-in) | Встроенные имена Python | len, print, int |

Диаграмма демонстрирует порядок, в котором Python ищет переменные: от локальной области к встроенной. Понимание этой последовательности критично для работы замыканий и декораторов.
Когда Python встречает имя переменной, он ищет её именно в таком порядке. Нашёл в Local — отлично, дальше не ищет. Не нашёл — идёт в Enclosing, и так далее.
Именно уровень Enclosing и создаёт замыкания: когда внутренняя function ссылается на переменную из внешней, Python «замыкает» эту переменную внутри внутренней функции.
Примеры замыканий
Лучше один раз увидеть, чем сто раз услышать про теорию. Вот классический пример замыкания — счётчик:
def make_counter(): count = 0 def increment(): nonlocal count # говорим Python: это не новая переменная count += 1 return count return increment # Создаём счётчик counter = make_counter() print(counter()) # 1 print(counter()) # 2 print(counter()) # 3
Что здесь происходит? make_counter завершила работу, переменная count должна была исчезнуть, но внутренняя функция increment её «захватила». Теперь каждый вызов counter() увеличивает именно ту самую count, которая была создана при первом вызове make_counter.
Ключевое слово nonlocal говорит Python: «Не создавай новую локальную переменную count, используй ту, что в охватывающей области видимости.»
Ещё пример — function с «памятью»:
def make_multiplier(factor): def multiply(number): return number * factor # factor "запомнился" return multiply # Создаём разные множители double = make_multiplier(2) triple = make_multiplier(3) print(double(5)) # 10 print(triple(5)) # 15
Каждая из double и triple помнит свой собственный factor. Это и есть замыкание в действии — внутренняя захватила переменную из внешней и носит её с собой, куда бы ни пошла.
Что такое декоратор в Python
Теперь, когда мы понимаем замыкания, можно переходить к главному блюду — декораторам. Декоратор — это функция, которая принимает другую как аргумент и возвращает новую с расширенной функциональностью. По сути, это практическое применение замыканий в красивой обёртке.
Звучит сложно? На деле всё проще: декоратор берёт вашу function, «оборачивает» её в дополнительную логику (логгирование, проверку прав, измерение времени — что угодно) и возвращает обновленную версию.
Синтаксис с @
Python предоставляет синтаксический сахар для применения декораторов — символ @. Когда вы пишете:
@my_decorator def some_function(): pass
Python автоматически разворачивает это в:
def some_function(): pass some_function = my_decorator(some_function)
То есть символ @ — это просто красивая запись для вызова декоратора и присвоения результата обратно к имени функции. Ничего магического, просто удобная синтаксическая конструкция, которая делает код более читаемым и позволяет сразу видеть, какие «дополнения» применены.
Примеры простых декораторов
Теория — это хорошо, но без практики она мертва. Давайте разберём несколько классических примеров декораторов, которые решают реальные задачи.
Логгирование
Начнём с простого — декоратора для логгирования вызовов:
def logging_decorator(func): def wrapper(): print(f'Вызывается функция {func.__name__}') result = func() print(f'Функция {func.__name__} завершена') return result return wrapper @logging_decorator def say_hello(): print('Привет, мир!') return 'done' say_hello()
Результат:
Вызывается функция say_hello Привет, мир! Функция say_hello завершена
Что здесь происходит? Декоратор создаёт функцию-обёртку wrapper, которая добавляет логгирование до и после вызова исходной function. Замыкание «запоминает» исходную func, и мы можем её вызвать в нужный момент.
Измерение времени
Ещё более практичный пример — декоратор для измерения времени выполнения:
import time def benchmark(func): def wrapper(): start = time.time() result = func() end = time.time() print(f'Функция {func.__name__} выполнена за {end - start:.4f} секунд') return result return wrapper @benchmark def slow_function(): time.sleep(1) # имитируем долгую работу return 'готово' slow_function()
Этот декоратор засекает время до вызова, выполняет её, засекает время после и выводит разность. Простой способ профилировать код без изменения самих функций.
Продвинутые возможности декораторов
Предыдущие примеры работают только с функциями без параметров, что существенно ограничивает их применение. В реальной жизни function принимают аргументы и возвращают значения, поэтому декораторы должны уметь с этим работать.
*args, **kwargs
Чтобы декоратор мог работать с любой функцией, независимо от количества и типа её параметров, используют магические *args и **kwargs:
def universal_decorator(func): def wrapper(*args, **kwargs): print(f'Вызов {func.__name__} с аргументами: {args}, {kwargs}') result = func(*args, **kwargs) print(f'Результат: {result}') return result return wrapper @universal_decorator def add(a, b): return a + b @universal_decorator def greet(name, greeting="Привет"): return f'{greeting}, {name}!' add(2, 3) greet("Анна", greeting="Здравствуй")
*args захватывает все позиционные аргументы в кортеж, **kwargs — все именованные в словарь. Так декоратор становится универсальным.
functools.wraps
Есть одна проблема: после декорирования функция теряет свои метаданные — имя, документацию, аннотации. Для решения этой проблемы используют functools.wraps:
from functools import wraps def my_decorator(func): @wraps(func) # сохраняет метаданные исходной функции def wrapper(*args, **kwargs): print(f'Декорирую {func.__name__}') return func(*args, **kwargs) return wrapper
@wraps(func) копирует __name__, __doc__, __module__ и другие атрибуты из исходной функции в обёртку. Без этого отладка превращается в ад, потому что все декорированные будут называться wrapper.
Применение нескольких декораторов
Python позволяет применить к одной функции несколько декораторов одновременно — это называется «stacked decorators» или вложенные декораторы. На первый взгляд всё просто: ставишь несколько @ друг под другом. Но есть подводный камень, который часто становится вопросом на собеседованиях: в каком порядке декораторы применяются?
Порядок применения
Вот тут начинается путаница. Декораторы записываются сверху вниз, но применяются снизу вверх. Звучит запутанно? Давайте разберём на примере:
@decorator_a @decorator_b @decorator_c def my_function(): return "Исходная функция"
Python разворачивает это в:
def my_function(): return "Исходная функция" my_function = decorator_a(decorator_b(decorator_c(my_function)))
То есть сначала decorator_c оборачивает исходную функцию, затем decorator_b оборачивает результат, и наконец decorator_a оборачивает всё это целиком. При вызове функции первым сработает код из decorator_a, потом из decorator_b, затем из decorator_c, и только потом исходная функция.
Практический пример
Лучше один раз увидеть, чем сто раз услышать теорию:
from functools import wraps def bold(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) return f"{result}" return wrapper def italic(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) return f"{result}" return wrapper def underline(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) return f"{result}" return wrapper @bold @italic @underline def get_text(): return "Привет, мир!" print(get_text()) # Результат: Привет, мир!
Видите? Хотя @underline написан ближе всего к функции, его тег <u> оказался внутри. Порядок такой: bold → italic → underline → исходная функция → underline → italic → bold.
Зачем это нужно
Вложенные декораторы — не академическая абстракция, а практичный инструмент. Вот типичные сценарии:
Веб-разработка:
@app.route('/admin/users') @login_required @admin_required def manage_users(): return "Страница управления пользователями" Логгирование + кеширование: @benchmark # измеряет время @cache # кеширует результат @log_calls # логирует вызовы def expensive_calculation(n): return n ** 2 Валидация + retry-логика: @retry(max_attempts=3) @validate_input @handle_errors def api_call(data): return requests.post('/api', json=data)
Важность порядка
Порядок декораторов критически важен. Поменяйте местами — получите другое поведение или даже ошибки. Например, если поставить @cache перед @validate_input, то в кеш может попасть результат для невалидных данных.
Общее правило: декораторы, которые изменяют входные данные или проверяют их, должны быть ближе к функции. Декораторы, которые работают с результатом (логгирование, форматирование), — дальше от неё.
Отладка вложенных декораторов
При отладке кода с несколькими декораторами полезно временно убирать их по одному, чтобы понять, где возникает проблема. И обязательно используйте @wraps — без него все функции будут называться wrapper, что превратит отладку в кошмар.
Помните: каждый дополнительный декоратор — это ещё один уровень вложенности. С одной стороны, это мощь и гибкость. С другой — усложнение архитектуры. Как и везде в программировании, нужен баланс между возможностями и читаемостью кода.
Параметризованные декораторы (фабрики)
Иногда нужен декоратор, который сам принимает параметры. Например, декоратор для проверки прав доступа, где можно указать нужную роль. Тут архитектура усложняется: нужна function, которая принимает параметры декоратора и возвращает сам декоратор.

Диаграмма поясняет архитектуру параметризованного декоратора: сначала вызывается функция с параметрами, которая возвращает сам декоратор. Затем он оборачивает целевую функцию, добавляя дополнительное поведение.
def requires_permission(role): def decorator(func): def wrapper(*args, **kwargs): current_user_role = get_current_user_role() # предположим, такая функция есть if current_user_role != role: return f'Доступ запрещён. Требуется роль: {role}' return func(*args, **kwargs) return wrapper return decorator @requires_permission('admin') def delete_database(): return 'База данных удалена' @requires_permission('moderator') def ban_user(username): return f'Пользователь {username} заблокирован'
Что здесь происходит? requires_permission(‘admin’) — это вызов функции, которая возвращает декоратор. То есть Python сначала выполняет requires_permission(‘admin’), получает декоратор, а затем применяет его к функции delete_database.
Архитектура получается трёхслойная: function-фабрика → декоратор → обёртка → исходная функция. Выглядит сложно, но логика простая: каждый уровень «запоминает» свои данные через замыкания — роль, исходную функцию, аргументы вызова.
Такие декораторы особенно популярны в веб-фреймворках, где часто нужно указывать дополнительные параметры: роли доступа, методы HTTP, пути маршрутов.
Классы как декораторы
Не только функции могут быть декораторами — любой вызываемый объект подойдёт. Классы с методом __call__() тоже можно использовать как декораторы, и это особенно удобно когда нужно сохранять состояние между вызовами.механика класса-декоратора
Схема показывает, как работает декоратор на основе класса: при применении вызывается __init__, сохраняющий функцию, а при каждом вызове — __call__, который управляет логикой и вызывает исходную функцию. Такой подход позволяет сохранять состояние между вызовами.

Схема показывает, как работает декоратор на основе класса: при применении вызывается __init__, сохраняющий функцию, а при каждом вызове — __call__, который управляет логикой и вызывает исходную функцию. Такой подход позволяет сохранять состояние между вызовами.
Рассмотрим декоратор для мемоизации (кеширования результатов):
class Memoize: def __init__(self, func): self.func = func self.cache = {} def __call__(self, *args, **kwargs): # Создаём ключ для кеша из аргументов key = str(args) + str(sorted(kwargs.items())) if key not in self.cache: print(f'Вычисляем результат для {key}') self.cache[key] = self.func(*args, **kwargs) else: print(f'Возвращаем из кеша для {key}') return self.cache[key] @Memoize def expensive_calculation(n): """Имитируем долгое вычисление""" import time time.sleep(1) return n * n # Первый вызов -- медленный print(expensive_calculation(5)) # Вычисляем... # Второй вызов -- быстрый print(expensive_calculation(5)) # Из кеша!
Преимущество классового декоратора в том, что у него есть состояние — словарь cache, который сохраняется между вызовами. С функциональными декораторами такое тоже возможно, но код получается менее читаемым.
Класс-декоратор работает так: при создании (@Memoize) вызывается __init__, который сохраняет исходную функцию. При каждом вызове декорированной функции срабатывает __call__, который проверяет кеш и либо вычисляет результат, либо возвращает сохранённый.
Где применяются декораторы на практике
Декораторы — это не просто академическая концепция, а рабочий инструмент, который активно используется в реальных проектах. Вот основные сферы применения:
- Flask (маршруты) — @app.route(‘/users’) определяет URL-маршруты.
- Django (доступ) — @login_required, @permission_required для контроля доступа.
- pytest (фикстуры) — @pytest.fixture для подготовки тестовых данных.
- Логгирование — автоматическое логирование вызовов функций без изменения их кода.
- Метрики и мониторинг — сбор статистики времени выполнения, частоты вызовов.
- Кеширование — @lru_cache из functools для мемоизации результатов.
- Валидация — проверка типов аргументов и возвращаемых значений.
- Retry-логика — автоматические повторы при сбоях.
- Синхронизация — блокировки для многопоточного кода.
Популярность декораторов объясняется принципом DRY (Don’t Repeat Yourself) — вместо копирования однотипного кода везде, где он нужен, вы пишете его один раз в декораторе и применяете где угодно. Это делает код чище, понятнее и проще в сопровождении.
В современном Python-коде декораторы встречаются настолько часто, что умение их читать и писать — это базовый навык, а не продвинутая техника.
Ошибки, которых стоит избегать
Даже понимая принципы работы декораторов, легко наступить на грабли. Вот самые частые ошибки, которые превращают элегантный код в источник багов:
Забыть про *args и **kwargs — декоратор без них работает только с функциями без параметров. Попытка применить такой декоратор к function с аргументами приведёт к TypeError.
Не использовать @wraps — без него все декорированные function будут называться wrapper, что усложняет отладку и нарушает интроспекцию.
Не вызывать исходную функцию — классическая ошибка новичков: написать обёртку, добавить туда логику, но забыть вызвать func(). Функция просто перестанет работать.
Неправильно обрабатывать возвращаемые значения — если исходная функция что-то возвращает, обёртка тоже должна это вернуть. Иначе результат потеряется.
Путать декораторы с аргументами и без — @decorator и @decorator() работают по-разному. Первый применяется напрямую, второй сначала вызывается и возвращает декоратор.
Большинство этих ошибок решается использованием шаблона: функция-декоратор с @wraps, обёртка с *args, **kwargs, обязательный вызов исходной функции и возврат её результата. Этот шаблон покрывает 90% случаев использования декораторов.
Заключение
Декораторы — это не просто синтаксический сахар или показуха для собеседований. Это практическое применение замыканий, которое решает реальные задачи: убирает дублирование кода, повышает читаемость и делает программы более модульными. Подведем итоги:
- Декораторы — это функции, которые модифицируют другие функции. Они позволяют добавлять поведение без изменения исходного кода.
- Работают на базе замыканий и функций высшего порядка. Это даёт гибкость и чистоту архитектуры.
- Синтаксис с @ — просто удобное сокращение. Он делает код читаемым и прозрачным.
- Декораторы применяются в популярных фреймворках. Например, во Flask, Django, pytest.
- Ошибки при использовании легко избежать. Главное — помнить про *args, @wraps и возврат значений.
Если вы только начинаете осваивать профессию Python-разработчика, рекомендуем обратить внимание на подборку курсов по Python. В них вы найдёте как теоретические основы языка, так и практические задания, включая написание собственных декораторов.
Рекомендуем посмотреть курсы по Python
Курс | Школа | Цена | Рассрочка | Длительность | Дата начала | Ссылка на курс |
---|---|---|---|---|---|---|
Python — программист с нуля
|
Merion Academy
5 отзывов
|
Цена
15 900 ₽
26 500 ₽
|
От
1 325 ₽/мес
Рассрочка на 12 месяцев
|
Длительность
4 месяца
|
Старт
7 сентября
|
Ссылка на курс |
Профессия Python-разработчик
|
Eduson Academy
68 отзывов
|
Цена
Ещё -5% по промокоду
103 900 ₽
|
От
8 658 ₽/мес
|
Длительность
6 месяцев
|
Старт
29 августа
|
Ссылка на курс |
Профессия Python-разработчик
|
ProductStar
38 отзывов
|
Цена
Ещё -31% по промокоду
165 480 ₽
299 016 ₽
|
От
6 895 ₽/мес
|
Длительность
10 месяцев
|
Старт
в любое время
|
Ссылка на курс |
Курс Go-разработчик (Junior)
|
Level UP
35 отзывов
|
Цена
45 500 ₽
|
От
11 375 ₽/мес
|
Длительность
3 месяца
|
Старт
27 сентября
|
Ссылка на курс |
Профессия Python-разработчик
|
Skillbox
149 отзывов
|
Цена
Ещё -20% по промокоду
67 750 ₽
135 500 ₽
|
От
5 646 ₽/мес
9 715 ₽/мес
|
Длительность
12 месяцев
|
Старт
27 августа
|
Ссылка на курс |

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

Зачем системному администратору нужна автоматизация?
Ручное управление сетями уходит в прошлое. Узнайте, как автоматизация помогает системным администраторам справляться с задачами быстрее и эффективнее.

Enum в C#: избавляемся от магических чисел раз и навсегда
Что такое enum в C#, зачем он нужен и как с его помощью писать читаемый и безопасный код? Покажем практические приёмы, которые экономят часы отладки.

Системный анализ: какие тренды определяют будущее?
В 2025 году системный анализ переживает важные изменения: ИИ берет на себя рутину, документация становится гибче, а аналитикам нужны новые навыки. Разбираем ключевые тренды.