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

Ответ

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

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

import time
import functools

def log_execution_time(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__} выполнилась за {end_time - start_time:.4f} секунд")
        return result
    return wrapper

@log_execution_time
def calculate_factorial(n):
    if n <= 1:
        return 1
    return n * calculate_factorial(n-1)

print(calculate_factorial(5))
# Вывод:
# calculate_factorial выполнилась за ... секунд
# 120

Почему это важно:

  • Разделение ответственности: Позволяет вынести сквозную функциональность (логирование, кэширование, аутентификацию) из бизнес-логики.
  • Повторное использование: Один декоратор можно применить ко многим функциям.
  • Читаемость: Использование @decorator над функцией явно указывает на добавленное поведение.

Ключевые моменты:

  • Декораторы могут быть вложенными (@decorator1 @decorator2).
  • Декораторы с аргументами требуют дополнительного уровня вложенности функций.
  • functools.wraps используется для корректной работы интроспекции (например, help() и имя функции).

Ответ 18+ 🔞

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

Вот смотри, пример декоратора, который засекает время:

import time
import functools

def log_execution_time(func):
    @functools.wraps(func)  # Эта хуйня сохраняет нормальное имя функции, а то обзовётся wrapper и всё
    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

@log_execution_time
def calculate_factorial(n):
    if n <= 1:
        return 1
    return n * calculate_factorial(n-1)

print(calculate_factorial(5))
# Вывод:
# calculate_factorial выполнилась за ... секунд
# 120

А зачем это всё, спросишь? Да овердохуища причин!

  • Чтобы не мешать всё в одну кучу: Вместо того чтобы в каждой функции писать одно и то же — логирование, проверку прав, кэширование — ты эту поебень выносишь в декоратор. Бизнес-логика чистая, а всякие служебные штуки — отдельно. Удивление пиздец, как удобно.
  • Чтобы не повторяться: Написал один раз декоратор для логирования — и тыкай его на любые функции, как горячие пирожки. Экономия времени — ебать колотить.
  • Читаемость: Видишь над функцией @check_permissions — и сразу ясно, что тут проверка прав доступа. Не надо в код лезть, всё на виду.

Но есть нюансы, ёпта:

  • Их можно навешивать друг на друга, как гирлянды на ёлку (@decor1 @decor2).
  • Если хочешь передать декоратору аргументы (типа @retry(attempts=5)), то придётся ещё одну обёрточную функцию написать — голова заболит, но привыкнешь.
  • functools.wraps — это святое! Без него твоя декорированная функция будет называться wrapper, и help() по ней выдаст какую-то дичь. Так что не забывай, а то сам от себя охуеешь потом при отладке.