Как работают декораторы? Можно ли навешивать несколько декораторов на одну функцию?

Краткий ответ (для собеседования): Декоратор — это функция (или объект вызываемый как функция), которая принимает другую функцию, оборачивает её дополнительной логикой и возвращает новую функцию‑обёртку. Оператор @decorator — синтаксический сахар для вызова func = decorator(func). На одну функцию можно навешивать несколько декораторов, при этом они применяются снизу вверх, а выполняются как вложенные обёртки — от внешнего к внутреннему.

Подробный ответ

Развернутое объяснение

Базовая идея работы декоратора: «взять готовую функцию и добавить к ней расширенное поведение, не меняя её исходный код».

def log_call(func):
   def wrapper(*args, **kwargs):
       print(f"Calling {func.__name__}")
       result = func(*args, **kwargs)
       print(f"{func.__name__} finished")
       return result
   return wrapper
   
@log_call
def process_order(order_id):
   print(f"Processing {order_id}")

Строка @log_call эквивалентна:

process_order = log_call(process_order)

То есть декоратор:

  1. принимает исходную функцию func;

  2. создаёт wrapper, который вызывает func и добавляет дополнительную логику до/после/вокруг вызова;

  3. возвращает wrapper вместо оригинальной функции.

В контексте backend‑разработки через декораторы часто реализуют логирование, кэширование, авторизацию, ретраи и т.п.

Можно ли навешивать несколько декораторов?

Да, можно. Python позволяет «складывать» декораторы над функцией:

@auth_required
@log_call
def get_user_profile(user_id):
   ...
# Это эквивалентно:
def get_user_profile(user_id):
   ...
get_user_profile = auth_required(log_call(get_user_profile))

То есть порядок такой: сначала log_call оборачивает исходную функцию, а потом auth_required оборачивает уже результат log_call.

Порядок выполнения декораторов

Важно различать:

Порядок применения (при определении): если на функцию навешано несколько декораторов, они применяются снизу вверх.

Порядок вызовов при исполнении: при выполнении несколько декораторов работают как вложенные обёртки: сначала вызывается внешний декоратор (верхний в списке), затем следующий, и в конце — исходная функция, получается «матрёшка».

def deco1(func):
   def wrapper(*args, **kwargs):
       print("deco1 before")
       result = func(*args, **kwargs)
       print("deco1 after")
       return result
   return wrapper
   
def deco2(func):
   def wrapper(*args, **kwargs):
       print("deco2 before")
       result = func(*args, **kwargs)
       print("deco2 after")
       return result
   return wrapper
   
@deco1
@deco2
def f():
   print("func")
   
f()

Результат будет таким:

deco1 before
deco2 before
func
deco2 after
deco1 after

То есть deco2 ближе к функции, deco1 — внешняя обёртка.

На собеседовании можно сформулировать

Несколько декораторов на одной функции работают как вложенные обёртки. Они применяются снизу вверх, а выполняются как матрёшка: внешний декоратор вызывается первым, потом внутренний и сама функция, затем коды «после вызова» идут от внутреннего к внешнему.

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

Читать полную статью в блоге