📖 Теория: декораторы

⚡ Кратко

Декоратор — функция, принимающая функцию и возвращающая новую функцию-обёртку. В обёртке принимают *args, **kwargs, вызывают оригинал и возвращают его результат. @functools.wraps сохраняет имя и документацию.

  • @my_decorator — синтаксический сахар для func = my_decorator(func).
  • Декораторы с аргументами — это функции, возвращающие декораторы (фабрики).
  • При стекировании декораторы выполняются снизу вверх.

Что такое декоратор

Декоратор — это функция высшего порядка, которая принимает другую функцию в качестве аргумента, добавляет ей дополнительное поведение и возвращает новую функцию. Внутри декоратора используется вложенная функция-обёртка (wrapper), поэтому декораторы неразрывно связаны с замыканиями.

def simple_decorator(func):
    def wrapper():
        print("Before call")
        func()
        print("After call")
    return wrapper


@simple_decorator
def say_hello():
    print("Hello!")


say_hello()

Где применяются декораторы

  • Логирование вызовов функций — запись аргументов и результатов в файл или консоль.
  • Измерение времени выполнения — полезно при профилировании и оптимизации.
  • Ограничение частоты вызовов (Throttling) — защита от спама, сетевые запросы.
  • Проверка и валидация входных данных — API, формы, обработка ошибок.
  • Автоматическое повторение выполнения (Retry) — повтор при сбоях сети или файловых операций.
  • Ограничение доступа — аутентификация и авторизация.
  • Кэширование результатов — ускорение повторных вычислений.
  • Автоматическое изменение данных — форматирование текста, приведение регистра.

Декораторы для функций с аргументами

Декорируемые функции часто принимают аргументы. Чтобы декоратор работал с любыми функциями, обёртка должна принимать *args, **kwargs и передавать их дальше.

import logging

logging.basicConfig(
    filename="functions.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    encoding="utf-8",
)


def log_decorator(func):
    def wrapper(*args, **kwargs):
        logging.info(f"Function {func.__name__} called with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        logging.info(f"Function {func.__name__} returned {result}")
        return result
    return wrapper


@log_decorator
def add(a: int, b: int) -> int:
    return a + b


@log_decorator
def say_hello() -> None:
    print("Привет!")


print(add(3, 5))
say_hello()

Возврат результата из вложенной функции

Если оригинальная функция возвращает значение, декоратор должен либо вернуть его, либо заменить на другой результат. Если из wrapper ничего не вернуть, результат будет потерян и вернётся None.

def upper_decorator(func):
    def wrapper(*args, **kwargs):
        print("Выполняем функцию и возвращаем результат")
        return func(*args, **kwargs).upper()
    return wrapper


@upper_decorator
def get_text() -> str:
    return "hello"


result = get_text()
print("Результат:", result)

Декоратор functools.wraps

Без дополнительных мер декорированная функция теряет своё имя и документацию, потому что им заменяет внутренняя функция wrapper. functools.wraps(func) копирует метаданные оригинала в обёртку.

import functools


def simple_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("\nДекорированная функция")
        return func(*args, **kwargs)
    return wrapper


@simple_decorator
def example_function():
    """Это оригинальная функция."""
    print("Привет!")


print("Имя:", example_function.__name__)
print("Документация:", example_function.__doc__)
example_function()

Декораторы с аргументами

Чтобы декоратор принимал собственные аргументы, создаётся функция-декоратор-фабрика, которая возвращает сам декоратор.

def message_decorator(message: str):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(message)
            return func(*args, **kwargs)
        return wrapper
    return decorator


@message_decorator("Начинаем выполнение")
def analyse_data() -> None:
    print("Данные проанализированы")


analyse_data()

Использование нескольких декораторов

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

def border_decorator(func):
    def wrapper(*args, **kwargs):
        print("*" * 40)
        func(*args, **kwargs)
        print("*" * 40)
    return wrapper


def repeat_decorator(times: int = 3):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                func(*args, **kwargs)
        return wrapper
    return decorator


@border_decorator
@repeat_decorator(3)
def print_line() -> None:
    print("-" * 40)


print_line()
Проверить по документации: полный синтаксис декораторов — в глоссарии Python и functools.wraps.