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

Что такое декоратор с параметром?

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

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Декоратор с параметром

Декоратор с параметром (parameterized decorator) — это функция, которая принимает аргументы для конфигурации поведения, а затем возвращает сам декоратор. Это позволяет настраивать, как декоратор обрабатывает функцию или метод.

Различие между обычным и параметризованным декоратором

Обычный декоратор (без параметров)

# Простой декоратор без параметров
def simple_decorator(func):
    """Оборачивает функцию логированием"""
    def wrapper(*args, **kwargs):
        print(f"Вызов функции {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@simple_decorator
def greet(name: str):
    return f"Hello, {name}!"

print(greet("Alice"))  # Выводит "Вызов функции greet"

Параметризованный декоратор

# Декоратор принимает параметры
def parametrized_decorator(prefix: str):
    """Декоратор с параметром prefix"""
    def decorator(func):
        """Это уже сам декоратор"""
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            return f"{prefix}: {result}"
        return wrapper
    return decorator

# Использование с параметром
@parametrized_decorator(prefix=">>> ")
def greet(name: str):
    return f"Hello, {name}!"

print(greet("Alice"))  # >>> Hello, Alice!

Структура декоратора с параметрами

# Трёхуровневая вложенность:
# 1. Функция параметров
# 2. Сам декоратор
# 3. Обёртка функции

def outer_parameters(param1, param2):
    """Уровень 1: принимает параметры конфигурации"""
    print(f"Инициализация декоратора: param1={param1}, param2={param2}")
    
    def decorator(func):
        """Уровень 2: сам декоратор"""
        print(f"Применение декоратора к функции {func.__name__}")
        
        def wrapper(*args, **kwargs):
            """Уровень 3: обёртка функции"""
            print(f"Выполнение функции с параметрами {args}, {kwargs}")
            return func(*args, **kwargs)
        
        return wrapper
    return decorator

# Применение
@outer_parameters("config1", "config2")
def my_function(x):
    return x * 2

my_function(5)

# Вывод:
# Инициализация декоратора: param1=config1, param2=config2
# Применение декоратора к функции my_function
# Выполнение функции с параметрами (5,), {}

Практические примеры

Пример 1: Retry декоратор с параметрами

import time
import functools
from typing import Callable

def retry(max_attempts: int = 3, delay: float = 1.0):
    """Декоратор для повтора функции при ошибке"""
    def decorator(func: Callable):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_error = None
            for attempt in range(1, max_attempts + 1):
                try:
                    print(f"Попытка {attempt}/{max_attempts}")
                    return func(*args, **kwargs)
                except Exception as e:
                    last_error = e
                    print(f"Ошибка: {e}")
                    if attempt < max_attempts:
                        print(f"Ждём {delay} сек перед повтором...")
                        time.sleep(delay)
            raise last_error
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def unstable_api_call():
    import random
    if random.random() < 0.7:
        raise ConnectionError("API недоступен")
    return "Успешно!"

try:
    result = unstable_api_call()
    print(result)
except Exception as e:
    print(f"Всё попыткам исчерпаны: {e}")

Пример 2: Rate limiting декоратор

import time
from functools import wraps

def rate_limit(max_calls: int, time_window: float):
    """Ограничивает количество вызовов в единицу времени"""
    def decorator(func):
        calls = []
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            now = time.time()
            # Удаляем старые вызовы
            calls[:] = [call_time for call_time in calls 
                       if call_time > now - time_window]
            
            if len(calls) >= max_calls:
                raise RuntimeError(
                    f"Превышено {max_calls} вызовов за {time_window} сек"
                )
            
            calls.append(now)
            return func(*args, **kwargs)
        
        return wrapper
    return decorator

@rate_limit(max_calls=5, time_window=10)
def api_endpoint():
    return "API ответ"

# Вызовем 5 раз - OK
for i in range(5):
    print(api_endpoint())

# Шестой вызов вызовет ошибку
try:
    api_endpoint()
except RuntimeError as e:
    print(f"Ошибка: {e}")

Пример 3: Валидация аргументов

from typing import Callable, get_type_hints
from functools import wraps

def validate_types(**type_checks):
    """Валидирует типы аргументов функции"""
    def decorator(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Получить имена параметров функции
            import inspect
            sig = inspect.signature(func)
            bound_args = sig.bind(*args, **kwargs)
            bound_args.apply_defaults()
            
            # Проверить типы
            for arg_name, expected_type in type_checks.items():
                if arg_name in bound_args.arguments:
                    value = bound_args.arguments[arg_name]
                    if not isinstance(value, expected_type):
                        raise TypeError(
                            f"Аргумент {arg_name} должен быть "
                            f"{expected_type.__name__}, "
                            f"получено {type(value).__name__}"
                        )
            
            return func(*args, **kwargs)
        
        return wrapper
    return decorator

@validate_types(name=str, age=int)
def create_user(name: str, age: int):
    return f"User: {name}, Age: {age}"

print(create_user("Alice", 25))  # OK

try:
    create_user("Bob", "30")  # Ошибка: age должен быть int
except TypeError as e:
    print(f"Ошибка: {e}")

Пример 4: Кэширование с TTL

import time
from functools import wraps

def cache_with_ttl(ttl_seconds: int):
    """Кэширует результат функции на определённое время"""
    def decorator(func):
        cache = {}
        cache_time = {}
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = str(args) + str(kwargs)
            now = time.time()
            
            # Проверить, есть ли кэш и не устарел ли
            if key in cache and now - cache_time[key] < ttl_seconds:
                print(f"Возвращаю из кэша (возраст: {now - cache_time[key]:.1f}s)")
                return cache[key]
            
            # Вычислить результат
            print(f"Вычисляю результат для {func.__name__}{args}")
            result = func(*args, **kwargs)
            
            # Сохранить в кэш
            cache[key] = result
            cache_time[key] = now
            
            return result
        
        return wrapper
    return decorator

@cache_with_ttl(ttl_seconds=5)
def expensive_computation(n: int):
    """Дорогостоящее вычисление"""
    time.sleep(2)  # Имитация длительного процесса
    return n * n

print(expensive_computation(5))  # Вычисляет (2s)
print(expensive_computation(5))  # Из кэша (0s)
time.sleep(6)
print(expensive_computation(5))  # Кэш устарел, вычисляет (2s)

Пример 5: Логирование с конфигурацией

import functools
import time
from enum import Enum

class LogLevel(Enum):
    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"

def log_call(level: LogLevel = LogLevel.INFO, prefix: str = ""):
    """Логирует вызовы функции"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            
            # Логирование входа
            log_msg = f"[{level.value}] {prefix} Вызов {func.__name__}"
            print(log_msg)
            
            try:
                # Выполнение
                result = func(*args, **kwargs)
                elapsed = time.time() - start
                
                # Логирование выхода
                print(f"[{level.value}] {prefix} Завершено за {elapsed:.3f}s")
                return result
            
            except Exception as e:
                elapsed = time.time() - start
                print(f"[ERROR] {prefix} Ошибка за {elapsed:.3f}s: {e}")
                raise
        
        return wrapper
    return decorator

@log_call(level=LogLevel.DEBUG, prefix="API")
def fetch_data(url: str):
    time.sleep(0.5)
    return {"status": "success"}

fetch_data("https://example.com")

# Вывод:
# [DEBUG] API Вызов fetch_data
# [DEBUG] API Завершено за 0.501s

Использование functools.wraps

from functools import wraps

# ❌ Без wraps - теряется информация
def bad_decorator(param):
    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    return decorator

# ✅ С wraps - сохраняется информация
def good_decorator(param):
    def decorator(func):
        @wraps(func)  # Копирует __name__, __doc__, __annotations__
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    return decorator

@good_decorator("param")
def my_function():
    """Документация функции"""
    pass

print(my_function.__name__)  # my_function (без wraps было wrapper)
print(my_function.__doc__)   # Документация функции (без wraps было None)

Декоратор с опциональными параметрами

from functools import wraps
from typing import Callable, Optional

def flexible_decorator(func: Optional[Callable] = None, *, enabled: bool = True):
    """Можно использовать с параметрами или без"""
    def decorator(f: Callable):
        @wraps(f)
        def wrapper(*args, **kwargs):
            if enabled:
                print(f"Выполняю {f.__name__}")
            return f(*args, **kwargs)
        return wrapper
    
    # Если функция передана напрямую (без параметров)
    if func is not None:
        return decorator(func)
    
    # Если передана как @flexible_decorator() или @flexible_decorator(enabled=False)
    return decorator

# Использование 1: без параметров
@flexible_decorator
def func1():
    return "func1"

# Использование 2: с параметрами
@flexible_decorator(enabled=False)
def func2():
    return "func2"

func1()  # Выводит "Выполняю func1"
func2()  # Ничего не выводит

Декораторы с параметрами — мощный инструмент для создания гибких и переиспользуемых решений в Python. Они позволяют написать один декоратор для множества разных случаев использования.

Что такое декоратор с параметром? | PrepBro