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

Что такое фабрика декораторов?

3.0 Senior🔥 81 комментариев
#Python Core#Архитектура и паттерны

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

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

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

Фабрика декораторов

Фабрика декораторов — это паттерн проектирования, который позволяет создавать и применять декораторы параметризованным способом. Фабрика производит готовые декораторы на основе переданных параметров, вместо прямого использования одного и того же декоратора.

Основная идея

Вместо статичного декоратора, который всегда делает одно и то же, фабрика позволяет создать декоратор с определёнными параметрами. Это особенно полезно, когда нужна переиспользуемая логика декорирования с разными конфигурациями.

Базовый пример: простой декоратор vs фабрика

# ПРОСТОЙ ДЕКОРАТОР - одна конфигурация
def timer_decorator(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Время выполнения: {end - start:.2f}с")
        return result
    return wrapper

@timer_decorator
def process_data():
    import time
    time.sleep(1)

# ФАБРИКА ДЕКОРАТОРОВ - параметризованная
def timer_factory(precision=2):
    """Фабрика создаёт декоратор с параметром precision"""
    def timer_decorator(func):
        def wrapper(*args, **kwargs):
            import time
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print(f"Время: {end - start:.{precision}f}с")
            return result
        return wrapper
    return timer_decorator

# Теперь можно использовать с разной точностью
@timer_factory(precision=2)
def fast_task():
    import time
    time.sleep(0.1)

@timer_factory(precision=4)
def slow_task():
    import time
    time.sleep(1)

Пример: фабрика декораторов для логирования

import logging
from functools import wraps
from typing import Callable, Any

def logging_factory(level=logging.INFO, prefix=""):
    """Фабрика создаёт декоратор логирования с параметрами"""
    def logging_decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            logger = logging.getLogger(func.__module__)
            
            log_msg = f"{prefix} Вызов {func.__name__} с args={args}, kwargs={kwargs}"
            logger.log(level, log_msg)
            
            try:
                result = func(*args, **kwargs)
                logger.log(level, f"{prefix} {func.__name__} вернул {result}")
                return result
            except Exception as e:
                logger.error(f"{prefix} {func.__name__} выбросил {type(e).__name__}: {e}")
                raise
        
        return wrapper
    return logging_decorator

# Использование с разными параметрами
@logging_factory(level=logging.DEBUG, prefix="[DB]")
def database_operation():
    return "Данные загружены"

@logging_factory(level=logging.ERROR, prefix="[AUTH]")
def authenticate(username):
    return True

database_operation()
authenticate("john")

Пример: фабрика декораторов для валидации

from functools import wraps
from typing import Callable, Any, List

def validate_factory(arg_types: dict):
    """Фабрика создаёт декоратор валидации типов"""
    def validator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            # Проверяем kwarg типы
            for param, expected_type in arg_types.items():
                if param in kwargs:
                    value = kwargs[param]
                    if not isinstance(value, expected_type):
                        raise TypeError(
                            f"Параметр {param} должен быть {expected_type.__name__}, "
                            f"а получен {type(value).__name__}"
                        )
            return func(*args, **kwargs)
        return wrapper
    return validator

@validate_factory({"age": int, "email": str, "scores": list})
def create_user(age, email, scores):
    return f"Пользователь {email} создан"

# Работает
print(create_user(age=25, email="user@example.com", scores=[90, 85, 88]))

# Ошибка валидации
try:
    create_user(age="twenty-five", email="user@example.com", scores=[90])
except TypeError as e:
    print(f"Ошибка: {e}")

Пример: фабрика для кэширования

from functools import wraps
from typing import Callable, Any
import time

def cache_factory(ttl: int = 60):
    """Фабрика создаёт декоратор с TTL (время жизни кэша)"""
    def cache_decorator(func: Callable) -> Callable:
        cache = {}
        timestamps = {}
        
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            key = (args, tuple(sorted(kwargs.items())))
            current_time = time.time()
            
            # Проверяем, есть ли кэш и не истёк ли его TTL
            if key in cache and current_time - timestamps[key] < ttl:
                print(f"Возврат из кэша для {func.__name__}")
                return cache[key]
            
            # Вычисляем результат
            result = func(*args, **kwargs)
            cache[key] = result
            timestamps[key] = current_time
            return result
        
        return wrapper
    return cache_decorator

@cache_factory(ttl=5)
def expensive_calculation(x, y):
    print(f"Вычисляю {x} + {y}...")
    time.sleep(1)  # Имитация долгого расчёта
    return x + y

print(expensive_calculation(5, 10))  # Вычисляет
print(expensive_calculation(5, 10))  # Из кэша
time.sleep(6)  # Ждём TTL
print(expensive_calculation(5, 10))  # Снова вычисляет (кэш истёк)

Пример: фабрика для retry логики

from functools import wraps
import time
from typing import Callable, Type

def retry_factory(max_attempts: int = 3, delay: float = 1.0, exceptions: tuple = (Exception,)):
    """Фабрика создаёт декоратор повторных попыток"""
    def retry_decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_attempts:
                        raise
                    print(f"Попытка {attempt}/{max_attempts} не удалась: {e}. "
                          f"Повтор через {delay}с...")
                    time.sleep(delay)
        return wrapper
    return retry_decorator

@retry_factory(max_attempts=3, delay=0.5, exceptions=(ValueError, ConnectionError))
def unreliable_api_call(url):
    import random
    if random.random() < 0.7:
        raise ConnectionError("Сервер не ответил")
    return f"Данные с {url}"

try:
    print(unreliable_api_call("https://api.example.com"))
except ConnectionError:
    print("Все попытки исчерпаны")

Практический пример: комбинирование фабрик

def combined_example():
    # Один метод может использовать несколько фабрик
    @retry_factory(max_attempts=2)
    @cache_factory(ttl=10)
    @logging_factory(prefix="[API]")
    def fetch_user_data(user_id: int):
        print(f"Загрузка данных пользователя {user_id}...")
        return {"id": user_id, "name": "John"}
    
    print(fetch_user_data(1))
    print(fetch_user_data(1))  # Из кэша

Преимущества фабрики декораторов

  • Переиспользуемость: Один код создаёт много вариаций
  • Гибкость: Параметры передаются в момент создания
  • Читаемость: Явно указаны конфигурации
  • Композиция: Можно комбинировать несколько фабрик
  • Тестируемость: Легче тестировать разные варианты

Вывод

Фабрика декораторов — это мощный паттерн для создания переиспользуемых и параметризованных декораторов. Она особенно полезна в больших системах, где нужна гибкость в применении одной и той же логики с разными конфигурациями.