Паттерн Декоратор в Python
Основная идея паттерна декоратор — обёртывание: создавая объект-обёртку (декоратор), который оборачивает исходный объект, тем самым расширяя его функциональность без изменения исходного объекта.
Базовые понятия
Паттерн декоратор состоит из четырёх основных ролей:
- Интерфейс компонента (Component): определяет общий интерфейс для декорируемого объекта и декоратора
- Конкретный компонент: исходный объект, который нужно декорировать
- Базовый класс декоратора (Decorator): хранит ссылку на объект компонента и реализует интерфейс компонента
- Конкретный декоратор: реализует конкретную функциональность декорирования

Базовый синтаксис декораторов
Декораторы функций
Декораторы функций — самая распространённая форма декораторов. Они принимают функцию в качестве аргумента и возвращают новую функцию.
Пример
def wrapper(*args, **kwargs):
print("Некоторые операции перед выполнением функции")
result = func(*args, **kwargs)
print("Некоторые операции после выполнения функции")
return result
return wrapper
@my_decorator
def say_hello(name):
print(f"Привет, {name}!")
# Использовать декоратор
say_hello("Алиса")
Вывод:
函数执行前的一些操作 Hello, Alice! 函数执行后的一些操作
Декораторы с параметрами
Если вам нужно передать параметры в декоратор, нужно вложить еще один уровень функций:
Пример
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(times):
print(f"Execution {i+1}:")
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Привет, {name}!")
greet("Bob")
Декораторы классов
Помимо декораторов функций, Python также поддерживает декораторы классов. Декораторы классов работают путем реализации__call__методов для работы.
Базовый декоратор класса
Пример
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
import time
start_time = time.time()
result = self.func(*args, **kwargs)
end_time = time.time()
print(f"Функция {self.func.__name__} выполнена за {end_time - start_time:.4f} секунд")
return result
@TimerDecorator
def calculate_sum(n):
return sum(range(n))
result = calculate_sum(1000000)
print(f"Результат вычисления: {result}")
Декоратор-класс с параметрами
Пример
def __init__(self, level="INFO"):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print(f"[{self.level}] Вызов функции: {func.__name__}")
print(f"[{self.level}] Аргументы: args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"[{self.level}] Возвращаемое значение: {result}")
return result
return wrapper
@LogDecorator(level="DEBUG")
def multiply(a, b):
return a * b
multiply(5, 3)
Встроенные декораторы
Python предоставляет несколько полезных встроенных декораторов:
@staticmethod и @classmethod
Пример
@staticmethod
def add(x, y):
return x + y
@classmethod
def multiply(cls, x, y):
return x * y
# Использовать статический метод
result1 = Calculator.add(5, 3)
print(f"Результат статического метода: {result1}")
# Использовать метод класса
result2 = Calculator.multiply(5, 3)
print(f"Результат метода класса: {result2}")
@property
Пример
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Радиус должен быть положительным числом")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(f"Радиус: {circle.radius}")
print(f"Площадь: {circle.area}")
circle.radius = 10
print(f"Новый радиус: {circle.radius}")
print(f"Новая площадь: {circle.area}")
Практические сценарии применения декораторов
1. Логирование
Пример
def wrapper(*args, **kwargs):
print(f"Начало выполнения: {func.__name__}")
try:
result = func(*args, **kwargs)
print(f"Успешно завершено: {func.__name__}")
return result
except Exception as e:
print(f"Выполнение не удалось: {func.__name__}, ошибка: {e}")
raise
return wrapper
@log_execution
def process_data(data):
# Имитация обработки данных
if not data:
raise ValueError("Данные не могут быть пустыми")
return [x * 2 for x in data]
#
data = [1, 2, 3]
result = process_data(data)
print(f"Результат обработки: {result}")
2. Проверка разрешений
Пример
def wrapper(user, *args, **kwargs):
if not user.get('is_authenticated', False):
raise PermissionError("Пользователь не авторизован")
return func(user, *args, **kwargs)
return wrapper
@require_login
def view_profile(user):
return f"Просмотр профиля пользователя {user['username']}"
#
user1 = {'username': 'alice', 'is_authenticated': True}
user2 = {'username': 'bob', 'is_authenticated': False}
print(view_profile(user1)) # Обычное выполнение
# print(view_profile(user2)) # вызовет PermissionError
3. Декоратор кэширования
Пример
cache = {}
def wrapper(*args):
if args in cache:
print(f"Получаем результат из кэша: {args}")
return cache[args]
result = func(*args)
cache[args] = result
print(f"Вычисляем и кэшируем результат: {args} -> {result}")
return result
return wrapper
@cache_results
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Проверка эффекта кэширования
print(fibonacci(5))
print(fibonacci(5)) # На этот раз оно будет взято из кэша
Порядок выполнения нескольких декораторов
При использовании нескольких декораторов порядок их выполнения — снизу вверх:
Пример
def wrapper():
print("Декоратор 1 - до")
func()
print("Декоратор 1 - после")
return wrapper
def decorator2(func):
def wrapper():
print("Декоратор 2 - до")
func()
print("Декоратор 2 - после")
return wrapper
@decorator1
@decorator2
def my_function():
print("Original function")
my_function()
Вывод:
装饰器1 - 前 装饰器2 - 前 原始函数 装饰器2 - 后 装饰器1 - 后
Сохранение метаданных функции
При использовании декораторов метаданные исходной функции (такие как имя функции, строка документации и т. д.) перезаписываются функцией-обёрткой. Вы можете использоватьfunctools.wrapsчтобы сохранить эту информацию:
Пример
def preserve_metadata(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Docstring of the wrapper function"""
return func(*args, **kwargs)
return wrapper
@preserve_metadata
def example_function():
"""Это строка документации исходной функции"""
pass
print(f"Имя функции: {example_function.__name__}")
print(f"Строка документации: {example_function.__doc__}")
Практические упражнения
Упражнение 1: Создание декоратора мониторинга производительности
Напишите декоратор для мониторинга времени выполнения и использования памяти функции.
Пример
import tracemalloc
def performance_monitor(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Начать отслеживание памяти
tracemalloc.start()
# Записать время начала
start_time = time.time()
# Выполнить функцию
result = func(*args, **kwargs)
# Записать время окончания
end_time = time.time()
# Получить использование памяти
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"Отчёт о производительности для функции {func.__name__}:")
print(f"Время выполнения: {end_time - start_time:.4f} секунд")
print(f"Использование памяти: текущее {current/1024:.2f} KB, пиковое {peak/1024:.2f} KB")
return result
return wrapper
@performance_monitor
def process_large_data():
"""Имитация обработки больших объёмов данных"""
data = [i**2 for i in range(100000)]
return sum(data)
process_large_data()
Упражнение 2: Реализация декоратора механизма повторных попыток
Создайте декоратор, который автоматически выполняет повторные попытки заданное число раз при сбое выполнения функции.
Пример
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
print(f"Функция {func.__name__} завершилась с ошибкой, достигнуто максимальное количество повторных попыток")
raise
print(f"Функция {func.__name__} завершилась с ошибкой при попытке {attempts}: {e}")
print(f"Ожидание {delay} секунд перед повторной попыткой...")
time.sleep(delay)
return None
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def unstable_operation():
"""Имитирует нестабильную операцию"""
import random
if random.random() < 0.7: # Вероятность сбоя 70%
raise ValueError("Случайный сбой")
return "Операция выполнена успешно"
# Проверка механизма повторных попыток
result = unstable_operation()
print(f"Итоговый результат: {result}")
Итог
Декораторы Python — мощный и гибкий инструмент, который обеспечивает следующие преимущества:
- Повторное использование кода: Отделяет сквозные аспекты (такие как логирование, кэширование, валидация) от бизнес-логики
- Динамическое расширение: Добавляет новую функциональность без изменения исходного кода
- Соблюдайте краткость: Используйте
@синтаксис делает код более ясным и читаемым - Следует принципу открытости/закрытости: Открыт для расширения, закрыт для изменения
Лучшие практики использования декораторов:
- Использование
functools.wrapsСохраняйте метаданные функции - Соблюдайте принцип единственной ответственности декоратора
- Правильно обрабатывайте исключения в декораторах
- Обращайте внимание на порядок выполнения декораторов