← Назад к вопросам

Как создать декоратор с аргументами?

1.8 Middle🔥 161 комментариев
#Python Core

Комментарии (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__ метод
  • Композиция — комбинируй несколько декораторов
  • Порядок — применяются от нижнего к верхнему

Декораторы с аргументами — это мощный способ создавать переиспользуемую функциональность.