Декораторы: создание и применение обёрток для функций
Что такое декоратор и зачем он нужен? 🤔
Декоратор — это "обёртка" вокруг функции, которая изменяет или дополняет её поведение без изменения исходного кода. Это как супергеройский костюм для вашей функции! 💫
Где применяются декораторы:
- Логирование работы функций
- Замер времени выполнения
- Кеширование результатов
- Проверка прав доступа
- Обработка ошибок и повторные попытки
Как выглядит простейший декоратор? 🔍
Рассмотрим базовый пример:
def my_decorator(func):
def wrapper():
print("Что-то происходит перед вызовом функции")
func()
print("Что-то происходит после вызова функции")
return wrapper
@my_decorator
def say_hello():
print("Привет!")
say_hello()
Вывод:
Что-то происходит перед вызовом функции
Привет!
Что-то происходит после вызовом функции
👉 @my_decorator — это синтаксический сахар для say_hello = my_decorator(say_hello)
Декораторы с аргументами функции 🎛️
Что если наша функция принимает параметры? Используем *args и **kwargs:
def log_arguments(func):
def wrapper(*args, **kwargs):
print(f"Вызов функции {func.__name__} с аргументами: {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
@log_arguments
def add(a, b):
return a + b
print(add(2, 3))
Вывод:
Вызов функции add с аргументами: (2, 3), {}
5
Возвращаем значения из декорируемых функций 🔄
Важно не забыть вернуть результат оборачиваемой функции!
def uppercase(func):
def wrapper(*args, **kwargs):
original_result = func(*args, **kwargs)
modified_result = original_result.upper()
return modified_result
return wrapper
@uppercase
def greet(name):
return f"Привет, {name}!"
print(greet("Данила"))
Вывод:
ПРИВЕТ, ДАНИЛА!
Практический пример: замер времени выполнения ⏱️
Создадим полезный декоратор для профилирования кода:
import time
def timer(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_something(n):
return sum(i * i for i in range(n))
calculate_something(1000000)
Пример вывода:
Функция 'calculate_something' выполнена за 0.1253 сек.
Цепочка декораторов: несколько обёрток 🎁
Декораторы можно накладывать друг на друга! Порядок имеет значение:
def bold(func):
def wrapper(*args, **kwargs):
return f"<b>{func(*args, **kwargs)}</b>"
return wrapper
def italic(func):
def wrapper(*args, **kwargs):
return f"<i>{func(*args, **kwargs)}</i>"
return wrapper
@bold
@italic
def hello(name):
return f"Привет, {name}"
print(hello("Мир"))
Вывод:
<b><i>Привет, Мир</i></b>
👉 Обратите внимание: порядок декораторов соответствует порядку оборачивания (bold применяется последним).
Декораторы с параметрами 🛠️
Иногда нужно передавать параметры самому декоратору. Для этого создаём "декоратор декораторов":
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(num_times=3)
def greet(name):
print(f"Привет, {name}!")
greet("Алексей")
Вывод:
Привет, Алексей!
Привет, Алексей!
Привет, Алексей!
Сохраняем метаданны функции с @functools.wraps 🏷️
При использовании декораторов теряется исходная информация о функции. Исправим это:
import functools
def debug(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__} с {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
@debug
def add(a, b):
"""Складывает два числа"""
return a + b
print(add.__name__) # 'add' (было бы 'wrapper' без functools.wraps)
print(add.__doc__) # 'Складывает два числа'
Реальный пример: кеширование результатов 🗃️
Реализуем мемоизацию с помощью декоратора:
from functools import wraps
def cache(func):
memo = {}
@wraps(func)
def wrapper(*args):
if args in memo:
print(f"Возвращаем кешированный результат для {args}")
return memo[args]
result = func(*args)
memo[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
👉 Этот декоратор ускоряет рекурсивные вычисления в разы!
Когда использовать декораторы? 🎯
- DRY (Don't Repeat Yourself) — выносите повторяющийся код в декораторы
- Разделение ответственности — бизнес-логика и вспомогательный код разделены
- Расширяемость — можно добавлять новую функциональность без изменения исходного кода
Помните: декораторы должны делать код чище, а не сложнее! Если обёрток становится слишком много — возможно, стоит пересмотреть архитектуру.