Декораторы в Python: полное руководство
Декораторы в Python: полное руководство
Декораторы — одна из мощнейших возможностей Python, позволяющая изменять поведение функций или классов без изменения их исходного кода. Они широко используются для добавления функциональности, такой как логирование, кэширование, проверка прав доступа и многое другое. В этой статье мы разберем, как создавать и применять декораторы, начиная с основ и заканчивая продвинутыми техниками.
Содержание
- Что такое декораторы?
- Базовый пример декоратора
- Синтаксис @
- Декораторы с аргументами
- Цепочки декораторов
- Классы как декораторы
- Сохранение метаданных с
functools.wraps - Практические примеры использования
- Частые ошибки и советы
- Заключение
Что такое декораторы?
Декоратор — это функция, которая принимает другую функцию (или класс) и возвращает новую функцию, обычно расширяя или изменяя поведение исходной. Декораторы применяются с помощью символа @ перед определением функции.
Основные идеи:
- Обёртывание функций: Декораторы “оборачивают” целевую функцию, добавляя код до или после её выполнения.
- Модификация поведения: Например, можно замерять время выполнения функции, проверять входные данные или логировать вызовы.
- Переиспользуемость: Декораторы позволяют выносить общую логику в отдельные компоненты.
Базовый пример декоратора
Рассмотрим простейший декоратор, который выводит сообщение до и после вызова функции.
def simple_decorator(func):
def wrapper():
print("До вызова функции")
func()
print("После вызова функции")
return wrapper
@simple_decorator
def greet():
print("Привет, мир!")
greet()
Вывод:
До вызова функции
Привет, мир!
После вызова функции
Как это работает:
simple_decoratorпринимает функциюgreetкак аргументfunc.- Внутри создается функция-обёртка
wrapper, которая вызываетfunc()междуprint. - Декоратор возвращает
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!
Разбор:
repeat(3)возвращает декораторdecorator.decoratorоборачиваетsay_helloвwrapper.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
Частые ошибки и советы
- Забыли использовать
@wraps: Это приводит к потере имени и документации функции. - Неправильная обработка аргументов: Всегда используйте
*args, **kwargsв обёртке. - Порядок декораторов: Помните, что декораторы применяются снизу вверх.
- Изменяемое состояние: Осторожно используйте переменные в замыканиях, если они изменяются.
Советы:
- Используйте декораторы для сквозной функциональности (logging, timing).
- Избегайте сложных декораторов, которые делают код менее читаемым.
Заключение
Декораторы — это гибкий инструмент для расширения функциональности кода без его модификации. Освоив их, вы сможете писать более чистый, модульный и переиспользуемый код. Практикуйтесь на реальных задачах: добавляйте логирование, замеряйте время выполнения, кэшируйте результаты, и вы быстро оцените мощь этого подхода.