Для чего нужен декоратор в Python?

Ответ

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

Основные применения:

  • Логирование: автоматическое логирование вызовов функций и их аргументов.
  • Кеширование (мемоизация): сохранение результатов дорогостоящих вызовов.
  • Контроль доступа: проверка прав пользователя перед выполнением функции.
  • Валидация: проверка входных данных или выходных результатов.
  • Замер времени выполнения: профилирование кода.
  • Повторные попытки (retry): автоматический повтор вызова при сбоях.

Пример декоратора для замера времени:

import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"Функция {func.__name__!r} выполнилась за {end_time - start_time:.4f} сек")
        return result
    return wrapper

@timer
def calculate_sum(n):
    return sum(range(n))

# При вызове будет выведено время выполнения
result = calculate_sum(1_000_000)

Декораторы помогают соблюдать принцип единственной ответственности (Single Responsibility Principle) и избегать дублирования кода.

Ответ 18+ 🔞

Давай разберёмся с этими декораторами, а то народ часто нихуя не понимает, что это за зверь такой.

Представь себе, что у тебя есть функция — ну, обычная, делает свои дела. А теперь ты хочешь, чтобы она перед работой логировала, кто её вызвал, или замеряла время, или проверяла права. Так вот, декоратор — это как раз та хитрая жопа, которая оборачивает твою функцию, не лезя внутрь её кода. Ничего не меняя, просто добавляет сверху какую-то логику. Красота, ёпта!

Где это реально пригождается, спросишь? Да овердохуища мест:

  • Логирование: чтобы автоматом писать в лог, кто, когда и с какими аргументами дернул функцию. Удобно, когда потом ищешь, где всё пошло по пизде.
  • Кеширование (или мемоизация): если функция, например, факториал считает, и ты её десять раз с одним аргументом вызываешь, зачем каждый раз пересчитывать? Декоратор запомнит результат и отдаст из кеша. Экономия, блядь, ресурсов — просто песня.
  • Контроль доступа: перед тем как дать доступ к админке, проверить, а не полупидор ли ты случайно. Если нет прав — нахуй иди.
  • Валидация данных: проверить, что на входе не хуй в пальто, а нормальные аргументы.
  • Замер времени выполнения: чтобы понять, какая функция жрёт процессорное время как не в себя.
  • Повторные попытки (retry): если функция упала из-за временной ошибки сети, декоратор может сам её перезапустить пару раз, прежде чем сдаться.

Вот тебе живой пример — декоратор, который засекает время работы функции:

import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"Функция {func.__name__!r} выполнилась за {end_time - start_time:.4f} сек")
        return result
    return wrapper

@timer
def calculate_sum(n):
    return sum(range(n))

# При вызове будет выведено время выполнения
result = calculate_sum(1_000_000)

Смотри, что тут происходит: мы написали функцию timer, которая принимает другую функцию (func). Внутри неё создаётся обёртка (wrapper), которая:

  1. Запоминает время старта.
  2. Вызывает оригинальную функцию.
  3. Запоминает время окончания.
  4. Печатает разницу.
  5. Возвращает результат оригинальной функции.

А эта магия @timer над calculate_sum — это просто красивый синтаксический сахар. Всё равно что написать calculate_sum = timer(calculate_sum). Функция calculate_sum теперь на самом деле — это wrapper, который и время засекает, и оригинальную функцию вызывает.

Зачем @functools.wraps(func)? А чтобы не потерять имя и документацию оригинальной функции. Без него после декорирования calculate_sum.__name__ был бы 'wrapper', и это, блядь, сбивало бы с толку при отладке.

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