Декораторы в Python: полное руководство

Декораторы в Python: полное руководство


Декораторы в Python: полное руководство

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

Содержание

  1. Что такое декораторы?
  2. Базовый пример декоратора
  3. Синтаксис @
  4. Декораторы с аргументами
  5. Цепочки декораторов
  6. Классы как декораторы
  7. Сохранение метаданных с functools.wraps
  8. Практические примеры использования
  9. Частые ошибки и советы
  10. Заключение

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

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

Основные идеи:

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

Базовый пример декоратора

Рассмотрим простейший декоратор, который выводит сообщение до и после вызова функции.

def simple_decorator(func):
    def wrapper():
        print("До вызова функции")
        func()
        print("После вызова функции")
    return wrapper

@simple_decorator
def greet():
    print("Привет, мир!")

greet()

Вывод:

До вызова функции
Привет, мир!
После вызова функции

Как это работает:

  1. simple_decorator принимает функцию greet как аргумент func.
  2. Внутри создается функция-обёртка wrapper, которая вызывает func() между print.
  3. Декоратор возвращает wrapper, заменяя исходную greet.

Синтаксис @

Символ @ — это синтаксический сахар. Запись:

@decorator
def func():
    ...

Эквивалентна:

def func():
    ...
func = decorator(func)

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

Иногда нужно передать параметры самому декоратору. Для этого создают декоратор, возвращающий другую функцию.

Пример: Декоратор с параметром для повторного выполнения функции

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    print("Hello!")

say_hello()

Вывод:

Hello!
Hello!
Hello!

Разбор:

  1. repeat(3) возвращает декоратор decorator.
  2. decorator оборачивает say_hello в wrapper.
  3. wrapper вызывает функцию n раз.

Цепочки декораторов

Декораторы можно применять последовательно. Порядок важен: декораторы выполняются снизу вверх.

def decorator1(func):
    def wrapper():
        print("Декоратор 1")
        func()
    return wrapper

def decorator2(func):
    def wrapper():
        print("Декоратор 2")
        func()
    return wrapper

@decorator1
@decorator2
def my_func():
    print("Исходная функция")

my_func()

Вывод:

Декоратор 1
Декоратор 2
Исходная функция

Классы как декораторы

Классы могут выступать в роли декораторов, если реализовать метод __call__.

Пример:

class Timer:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        import time
        start = time.time()
        result = self.func(*args, **kwargs)
        end = time.time()
        print(f"Время выполнения: {end - start} сек.")
        return result

@Timer
def long_running_func():
    time.sleep(2)

long_running_func()  # Вывод: Время выполнения: 2.0 сек.

Сохранение метаданных с functools.wraps

Декораторы заменяют метаданные исходной функции (например, __name__). Чтобы сохранить их, используйте functools.wraps.

from functools import wraps

def logged(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Вызов функции {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@logged
def example():
    """Документация функции example."""
    pass

print(example.__name__)  # example (без wraps было бы 'wrapper')
print(example.__doc__)   # Документация функции example.

Практические примеры использования

1. Логирование вызовов

def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Функция {func.__name__} вызвана с аргументами: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

2. Кэширование результатов

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

3. Проверка аутентификации

def requires_auth(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.is_authenticated:
            raise PermissionError("Требуется авторизация")
        return func(user, *args, **kwargs)
    return wrapper

Частые ошибки и советы

  1. Забыли использовать @wraps: Это приводит к потере имени и документации функции.
  2. Неправильная обработка аргументов: Всегда используйте *args, **kwargs в обёртке.
  3. Порядок декораторов: Помните, что декораторы применяются снизу вверх.
  4. Изменяемое состояние: Осторожно используйте переменные в замыканиях, если они изменяются.

Советы:

  • Используйте декораторы для сквозной функциональности (logging, timing).
  • Избегайте сложных декораторов, которые делают код менее читаемым.

Заключение

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