Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Декораторы с аргументами в Python
Декораторы с параметрами требуют дополнительного слоя функций. Вот как это работает.
1. Базовая структура
Декоратор с аргументами имеет три уровня функций:
def decorator_with_args(arg1, arg2):
"""Внешняя функция — получает аргументы"""
def decorator(func):
"""Средняя функция — получает функцию"""
def wrapper(*args, **kwargs):
"""Внутренняя функция — выполняет логику"""
print(f"Декоратор получил: {arg1}, {arg2}")
return func(*args, **kwargs)
return wrapper
return decorator
# Использование
@decorator_with_args("параметр1", "параметр2")
def my_function(x, y):
return x + y
result = my_function(5, 3)
print(result) # 8
2. Декоратор для логирования с уровнем
import functools
import logging
def log_with_level(level="INFO"):
"""Декоратор логирует вызовы функции с указанным уровнем"""
def decorator(func):
@functools.wraps(func) # Сохраняет метаданные функции
def wrapper(*args, **kwargs):
logger = logging.getLogger(func.__module__)
logger.log(
getattr(logging, level),
f"Вызвана функция {func.__name__} с args={args}, kwargs={kwargs}"
)
try:
result = func(*args, **kwargs)
logger.log(
getattr(logging, level),
f"Функция {func.__name__} вернула {result}"
)
return result
except Exception as e:
logger.log(
getattr(logging, level),
f"Функция {func.__name__} выбросила {type(e).__name__}: {e}"
)
raise
return wrapper
return decorator
# Использование
@log_with_level("DEBUG")
def divide(a, b):
return a / b
result = divide(10, 2) # DEBUG: Вызвана функция divide...
3. Декоратор для повторных попыток (retry)
import functools
import time
def retry(max_attempts=3, delay=1, backoff=2):
"""Повторно попытаться выполнить функцию при ошибке"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
attempt = 0
wait_time = delay
while attempt < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempt += 1
if attempt >= max_attempts:
print(f"Все {max_attempts} попытки исчерпаны")
raise
print(f"Ошибка: {e}. Повторная попытка через {wait_time}с...")
time.sleep(wait_time)
wait_time *= backoff # Экспоненциальный backoff
return wrapper
return decorator
# Использование
@retry(max_attempts=3, delay=1, backoff=2)
def unstable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("Нет соединения")
return "Успех!"
result = unstable_api_call()
4. Декоратор для проверки типов (validation)
import functools
def validate_types(**type_checks):
"""Проверить типы аргументов"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Получить имена параметров функции
import inspect
sig = inspect.signature(func)
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
# Проверить типы
for param_name, expected_type in type_checks.items():
if param_name in bound_args.arguments:
value = bound_args.arguments[param_name]
if not isinstance(value, expected_type):
raise TypeError(
f"Параметр '{param_name}' должен быть {expected_type}, "
f"получен {type(value)}"
)
return func(*args, **kwargs)
return wrapper
return decorator
# Использование
@validate_types(name=str, age=int, email=str)
def create_user(name, age, email):
return f"Пользователь {name} ({age}) создан"
result = create_user("John", 25, "john@example.com") # OK
# create_user("John", "25", "john@example.com") # TypeError
5. Декоратор для кэширования с TTL
import functools
import time
def cache_with_ttl(ttl_seconds=60):
"""Кэшировать результат функции на указанное время"""
def decorator(func):
cache = {}
cache_time = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Создать ключ кэша из аргументов
cache_key = (args, tuple(sorted(kwargs.items())))
# Проверить, есть ли в кэше и не истёк ли TTL
if cache_key in cache:
elapsed = time.time() - cache_time[cache_key]
if elapsed < ttl_seconds:
print(f"Вернули кэшированный результат")
return cache[cache_key]
else:
# Удалить устаревшие данные
del cache[cache_key]
del cache_time[cache_key]
# Выполнить функцию и кэшировать результат
result = func(*args, **kwargs)
cache[cache_key] = result
cache_time[cache_key] = time.time()
return result
return wrapper
return decorator
# Использование
@cache_with_ttl(ttl_seconds=5)
def expensive_calculation(x):
print(f"Вычисляю для {x}...")
time.sleep(1)
return x ** 2
print(expensive_calculation(5)) # Вычисляю для 5..., результат: 25
print(expensive_calculation(5)) # Вернули кэшированный результат, результат: 25
time.sleep(6)
print(expensive_calculation(5)) # Вычисляю для 5..., результат: 25 (кэш истёк)
6. Декоратор для ограничения частоты вызовов (rate limiting)
import functools
import time
def rate_limit(calls_per_second=1):
"""Ограничить частоту вызовов функции"""
min_interval = 1 / calls_per_second
last_called = [0]
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
if elapsed < min_interval:
time.sleep(min_interval - elapsed)
last_called[0] = time.time()
return func(*args, **kwargs)
return wrapper
return decorator
# Использование
@rate_limit(calls_per_second=2) # Максимум 2 вызова в секунду
def api_call():
print(f"API вызов в {time.time():.1f}")
for _ in range(5):
api_call()
# Будет выполняться с максимум 2 вызовами в секунду
7. Класс как декоратор с аргументами
import functools
class PermissionDecorator:
"""Декоратор для проверки прав доступа"""
def __init__(self, *required_permissions):
self.required_permissions = required_permissions
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Получить текущего пользователя (из контекста)
user = get_current_user() # условно
# Проверить права
if not all(user.has_permission(p) for p in self.required_permissions):
raise PermissionError(
f"Недостаточно прав. Требуется: {self.required_permissions}"
)
return func(*args, **kwargs)
return wrapper
# Использование
@PermissionDecorator('admin', 'edit_posts')
def delete_post(post_id):
print(f"Удаляю пост {post_id}")
# delete_post(123) # Проверит права
8. Комбинирование нескольких декораторов
@log_with_level("INFO")
@retry(max_attempts=3, delay=1)
@cache_with_ttl(ttl_seconds=60)
def fetch_user_data(user_id):
"""Кэширует, повторяет при ошибке, логирует"""
print(f"Загружаю пользователя {user_id}")
return {"id": user_id, "name": "John"}
# Порядок выполнения (от низу вверх при определении):
# 1. cache_with_ttl
# 2. retry
# 3. log_with_level
9. Декоратор с опциональными аргументами
import functools
def smart_decorator(func=None, *, enabled=True, prefix=""):
"""Декоратор, который работает с и без аргументов"""
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
if enabled:
print(f"{prefix}Вызов {f.__name__}")
return f(*args, **kwargs)
return wrapper
# Если вызван как @smart_decorator (без аргументов)
if func is not None:
return decorator(func)
# Если вызван как @smart_decorator() или @smart_decorator(enabled=False)
else:
return decorator
# Использование обоих способов
@smart_decorator
def func1():
pass
@smart_decorator(enabled=False, prefix="[LOG]")
def func2():
pass
Ключевые моменты
- Три уровня функций — внешняя получает аргументы, средняя получает функцию, внутренняя выполняет логику
- functools.wraps — сохраняет метаданные оригинальной функции
- Cache key из args — используй кортежи для hashable ключей
- Класс как декоратор — реализуй
__call__метод - Композиция — комбинируй несколько декораторов
- Порядок — применяются от нижнего к верхнему
Декораторы с аргументами — это мощный способ создавать переиспользуемую функциональность.