Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Плюсы и минусы декораторов
Декораторы — один из самых мощных и используемых инструментов Python. За 10+ лет практики я использую их в каждом проекте, но нужно понимать trade-offs.
1. Что такое декоратор
# Декоратор — функция, которая принимает функцию и возвращает функцию
def my_decorator(func):
def wrapper(*args, **kwargs):
print('Перед выполнением')
result = func(*args, **kwargs)
print('После выполнения')
return result
return wrapper
@my_decorator
def say_hello(name):
return f'Hello, {name}!'
# Эквивалентно:
# say_hello = my_decorator(say_hello)
2. ПЛЮСЫ Декораторов
2.1 Разделение ответственности (SoC)
# ПЛЮС: Отделяем cross-cutting concerns от основной логики
# Без декоратора (плохо)
def fetch_user_data(user_id):
print(f'Fetching user {user_id}...')
log_request(user_id)
validate_permission(user_id)
measure_time()
try:
# Основная логика затеряна в коде
user = db.query(f'SELECT * FROM users WHERE id = {user_id}')
return user
except Exception as e:
log_error(e)
raise
finally:
cleanup()
# С декораторами (хорошо)
@log_requests
@require_permission
@timing
@error_handler
def fetch_user_data(user_id):
# Только основная логика
return db.query(f'SELECT * FROM users WHERE id = {user_id}')
2.2 Переиспользование кода (DRY)
# ПЛЮС: Один декоратор применяем ко многим функциям
def retry(max_attempts=3):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
return wrapper
return decorator
# Применяем к разным функциям
@retry(max_attempts=3)
def call_external_api():
# Автоматическая переповтор
pass
@retry(max_attempts=5)
def fetch_from_db():
# Автоматическая переповтор с 5 попытками
pass
# DRY — не повторяем логику переповтора
2.3 Кэширование
# ПЛЮС: Легко кэшируем результаты дорогостоящих операций
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_computation(n):
# Результаты автоматически кэшируются
return sum(range(n))
expensive_computation(1000) # Вычисляется
expensive_computation(1000) # Возвращается из кэша O(1)
# Без декоратора нужно вручную управлять кэшем
2.4 Аутентификация и авторизация
# ПЛЮС: Безопасность отделена от логики
from functools import wraps
def require_admin(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
if not request.user.is_admin:
raise PermissionError('Admin access required')
return func(request, *args, **kwargs)
return wrapper
@require_admin
def delete_user(request, user_id):
# Функция не думает о проверке доступа
return db.delete_user(user_id)
@require_admin
def ban_user(request, user_id):
return db.ban_user(user_id)
# Логика авторизации в одном месте
2.5 Логирование и мониторинг
# ПЛЮС: Логируем без засорения основного кода
import logging
import time
def log_and_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
logging.info(f'Calling {func.__name__}')
try:
result = func(*args, **kwargs)
elapsed = time.time() - start
logging.info(f'{func.__name__} completed in {elapsed:.3f}s')
return result
except Exception as e:
elapsed = time.time() - start
logging.error(f'{func.__name__} failed after {elapsed:.3f}s: {e}')
raise
return wrapper
@log_and_time
def process_data():
# Чистая логика
pass
2.6 Валидация
# ПЛЮС: Валидация входных данных отделена
def validate_types(**expected_types):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Проверяем типы
for key, value in kwargs.items():
if key in expected_types:
if not isinstance(value, expected_types[key]):
raise TypeError(
f'{key} must be {expected_types[key].__name__}'
)
return func(*args, **kwargs)
return wrapper
return decorator
@validate_types(age=int, name=str)
def create_user(name: str, age: int):
# Не думаем о валидации
return {'name': name, 'age': age}
2.7 Параллельное выполнение
# ПЛЮС: Асинхронность/параллельность отделена
import asyncio
def async_wrapper(func):
def wrapper(*args, **kwargs):
# Преобразует синхронную функцию в асинхронную
return asyncio.run(func(*args, **kwargs))
return wrapper
@async_wrapper
async def fetch_data(url):
# Чистая бизнес-логика
pass
3. МИНУСЫ Декораторов
3.1 Усложнение отладки
# МИНУС: Сложно отследить стэк вызовов
def my_decorator(func):
def wrapper(*args, **kwargs): # Это где-то в стэке
return func(*args, **kwargs)
return wrapper
@my_decorator
def buggy_function():
raise ValueError('Ошибка') # Стэк будет показывать wrapper
# РЕШЕНИЕ: Используй @wraps
from functools import wraps
def my_decorator(func):
@wraps(func) # Копирует metadata
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
3.2 Производительность
# МИНУС: Каждый вызов декорируемой функции имеет overhead
import time
from functools import wraps
def timing_overhead(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time() # Overhead
result = func(*args, **kwargs)
elapsed = time.time() - start # Overhead
return result
return wrapper
@timing_overhead
def fast_function():
return 1 + 1 # Быстро, но декоратор добавляет 10-20% overhead
# Для критичного по скорости кода избегай часто вызываемых декораторов
3.3 Сложность понимания
# МИНУС: Много декораторов — трудно понять порядок выполнения
@decorator_1
@decorator_2
@decorator_3
def my_function():
pass
# Порядок выполнения: decorator_3 → decorator_2 → decorator_1 → функция
# Это не очевидно новичку
# Порядок в стэке вызовов (от внешнего к внутреннему):
# decorator_1 → decorator_2 → decorator_3 → my_function
3.4 Проблемы с типизацией
# МИНУС: Типы становятся неправильными
from typing import Callable
def my_decorator(func: Callable) -> Callable:
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def add(a: int, b: int) -> int:
return a + b
# IDE не знает, что add принимает int, int и возвращает int
# Используй @wraps и типизируй правильно
from functools import wraps
from typing import TypeVar, Callable
F = TypeVar('F', bound=Callable)
def my_decorator(func: F) -> F:
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper # type: ignore
3.5 Сложность при комбинировании
# МИНУС: Разные декораторы могут конфликтовать
def decorator_a(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper() # Предполагает str
return wrapper
def decorator_b(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return len(result) # Предполагает length
return wrapper
@decorator_a
@decorator_b
def get_data():
return 'test'
# decorator_b вернет 4 (длину)
# decorator_a попытается вызвать .upper() на 4 → Error
3.6 Состояние и побочные эффекты
# МИНУС: Декораторы с состоянием сложнее тестировать
class CountingDecorator:
def __init__(self, func):
self.func = func
self.count = 0 # Состояние!
def __call__(self, *args, **kwargs):
self.count += 1 # Побочный эффект
return self.func(*args, **kwargs)
# Это статефул, трудно тестировать и параллелизировать
3.7 Скрытые параметры и поведение
# МИНУС: Функция может иметь неожиданное поведение
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Добавляем параметр без согласования
kwargs['injected_param'] = 'hidden'
return func(*args, **kwargs)
return wrapper
@my_decorator
def my_function(a, b):
# Ждет 'a' и 'b', но декоратор добавляет 'injected_param'
# Это не видно из сигнатуры функции!
pass
# РЕШЕНИЕ: Явно задокументируй поведение
4. Таблица сравнения
"""
┌──────────────────────┬────────────────────┬──────────────────┐
│ Аспект │ ПЛЮСЫ │ МИНУСЫ │
├──────────────────────┼────────────────────┼──────────────────┤
│ Код │ DRY, чистый │ Сложнее │
│ Отладка │ SoC │ Трудно отследить │
│ Производительность │ Кэширование │ Overhead вызова │
│ Типизация │ Абстракция │ IDE потеряет type │
│ Тестирование │ Легче тестировать │ Сложнее с state │
│ Читаемость │ Декларативно │ Много скрытого │
└──────────────────────┴────────────────────┴──────────────────┘
"""
5. Лучшие практики
# 1. ВСЕГДА используй @wraps
from functools import wraps
def good_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# 2. Задокументируй поведение
def logged(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Логирует вызовы функции."""
logging.info(f'Calling {func.__name__}')
return func(*args, **kwargs)
return wrapper
# 3. Типизируй корректно
from typing import TypeVar, Callable
F = TypeVar('F', bound=Callable)
def typed_decorator(func: F) -> F:
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper # type: ignore
# 4. Минимизируй логику в декораторе
def thin_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Только необходимое
return func(*args, **kwargs)
return wrapper
# 5. Подумай, нужен ли декоратор
# Может быть композиция функций проще?
def apply_decorator_manually(func, decorator):
return decorator(func) # Явнее
6. Когда использовать декораторы
# ✅ ИСПОЛЬЗУЙ когда:
# 1. Cross-cutting concerns (logging, auth, timing)
@log_calls
@require_auth
def protected_api():
pass
# 2. Кэширование результатов
@lru_cache
def compute_something(x):
pass
# 3. Валидация/преобразование данных
@validate_input
def process_data(data):
pass
# 4. Переиспользование логики
@retry(3)
@timeout(30)
def external_call():
pass
# ❌ ИЗБЕГАЙ когда:
# 1. Основная логика функции
@my_complex_logic # Плохо
def process():
pass
# 2. Много скрытого поведения
@magic_decorator # Трудно понять что происходит
# 3. Критичная по производительности часть
# Избегай множественных декораторов на hot path
Заключение
Декораторы — мощный инструмент для:
- Разделения ответственности
- Переиспользования кода
- Обработки cross-cutting concerns
Но нужно быть осторожным с:
- Усложнением кода
- Влиянием на производительность
- Потерей типизации
Основное правило: используй декораторы для инфраструктуры, не для основной логики.