Что такое декоратор в Python?

Ответ

Декоратор в Python — это функция, которая принимает другую функцию (или класс) в качестве аргумента и возвращает новую, модифицированную функцию, обычно расширяя или изменяя её поведение. Это реализация паттерна проектирования «Декоратор», позволяющая добавлять новую функциональность к существующим объектам динамически и без изменения их исходного кода.

Зачем это нужно? Для сквозной функциональности (cross-cutting concerns), которую нужно применять ко многим функциям: логирование, тайминг, кэширование, проверка прав доступа, валидация аргументов.

Базовый пример создания и использования:

def timer_decorator(func):
    """Декоратор, замеряющий время выполнения функции."""
    import time
    def wrapper(*args, **kwargs):  # Принимает любые аргументы
        start_time = time.perf_counter()
        result = func(*args, **kwargs)  # Вызов оригинальной функции
        end_time = time.perf_counter()
        print(f"Функция {func.__name__} выполнилась за {end_time - start_time:.4f} сек.")
        return result
    return wrapper

# Применение декоратора с помощью синтаксиса @
@timer_decorator
def calculate_sum(n):
    """Вычисляет сумму чисел от 1 до n."""
    return sum(range(1, n+1))

# Вызов выглядит как обычно, но работает с доп. функциональностью
print(calculate_sum(1_000_000))
# Вывод:
# Функция calculate_sum выполнилась за 0.0453 сек.
# 500000500000

Встроенные и популярные декораторы:

  • @staticmethod, @classmethod, @property — для методов классов.
  • @functools.lru_cache(maxsize=128) — для мемоизации (кэширования) результатов функции.
  • @dataclasses.dataclass (Python 3.7+) — для автоматической генерации boilerplate-кода в классах.

Ответ 18+ 🔞

Вот тебе, дружище, история про декораторы в Python. Сидишь ты такой, пишешь код, и тут бац — понимаешь, что полсотни функций надо обернуть в одинаковую логику: засечь время, залогировать, проверить права. И тут тебя осеняет: «Ёпта, я же не буду это в каждую функцию копипастить, я ж не маньяк!» И вот тут на сцену выходит декоратор — хитрая жопа, которая позволяет навесить на любую функцию дополнительное поведение, как будто ты ей плащ-невидимку подарил, только наоборот.

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

Как эта магия устроена под капотом? По сути, декоратор — это просто функция, которая принимает другую функцию, делает с ней что-то и возвращает новую, улучшенную. Всё, блядь, гениальное просто.

Смотри на примере, который сам от себя охуевает от простоты. Допустим, мы хотим замерять, сколько выполняется любая функция.

def timer_decorator(func):  # Вот наш декоратор. Принимает функцию (func) как аргумент.
    """Декоратор, замеряющий время выполнения функции."""
    import time
    def wrapper(*args, **kwargs):  # А это обёртка. Она принимает ВСЕ аргументы, которые могли передать оригинальной функции.
        start_time = time.perf_counter()
        result = func(*args, **kwargs)  # Тут мы, наконец, вызываем оригинальную функцию с её родными аргументами.
        end_time = time.perf_counter()
        print(f"Функция {func.__name__} выполнилась за {end_time - start_time:.4f} сек.")
        return result  # И возвращаем её результат, как будто так и было.
    return wrapper  # Декоратор возвращает нашу обёрнутую функцию.

# Применяем эту красоту с помощью собачки @
@timer_decorator
def calculate_sum(n):
    """Вычисляет сумму чисел от 1 до n."""
    return sum(range(1, n+1))

# Вызываем как обычно, а под капотом — волшебство
print(calculate_sum(1_000_000))
# На экране будет:
# Функция calculate_sum выполнилась за 0.0453 сек.
# 500000500000

Видишь? Мы просто написали @timer_decorator над функцией, и теперь при каждом её вызове автоматически срабатывает наш таймер. Это же ёперный театр, как удобно! Можно навесить этот декоратор на что угодно, и всё будет работать.

А что там ещё есть готового, чтобы не изобретать велосипед? О, тут целая куча полезных штук, которые уже стоят в гараже, заправленные и готовые к бою.

  • @staticmethod, @classmethod, @property — это классика для работы с классами. Без них ни один уважающий себя ООП-шник на работу не выйдет.
  • @functools.lru_cache(maxsize=128) — вот это, блядь, мощнейшая штука. Она запоминает результаты вызовов функции для определённых аргументов. Второй раз с теми же аргументами — бац, и результат из кэша, а не вычисляется заново. Производительность взлетает, как ужаленная. Особенно для рекурсивных или тяжелых функций.
  • @dataclasses.dataclass (Python 3.7+) — это вообще песня. Хочешь создать класс, который в основном хранит данные? Раньше надо было писать тонну boilerplate-кода: __init__, __repr__, __eq__. А теперь навесил этот декоратор — и он всё сгенерирует сам. Удивление пиздец, как же раньше жили-то.

Короче, декораторы — это одна из тех фишек Python, после понимания которой хочется сказать «ни хуя себе» и начать использовать её везде. Главное — без фанатизма, а то можно так задекорировать, что и сам потом не разберёшься, что там под капотом творится.