Использовал ли декораторы в Python?

«Использовал ли декораторы в Python?» — вопрос из категории Python, который задают на 26% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Практические примеры из моего опыта:

  1. Логирование и профилирование:

    import time
    import functools
    from typing import Callable
    
    def log_execution_time(func: Callable) -> Callable:
        """Декоратор для логирования времени выполнения функции."""
        @functools.wraps(func)  # Важно: сохраняет метаданные оригинальной функции
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            end = time.perf_counter()
            print(f"[LOG] {func.__name__} executed in {end - start:.4f} seconds")
            return result
        return wrapper
    
    @log_execution_time
    def process_large_dataset(data: list):
        """Имитация долгой обработки."""
        time.sleep(0.5)
        return sum(data)
    
    # Использование
    process_large_dataset([1, 2, 3, 4, 5])
    # Вывод: [LOG] process_large_dataset executed in 0.5001 seconds
  2. Кеширование результатов тяжелых вычислений (используя @functools.lru_cache):

    from functools import lru_cache
    
    @lru_cache(maxsize=128)  # Кеширует до 128 уникальных вызовов
    def fibonacci(n: int) -> int:
        """Рекурсивное вычисление числа Фибоначчи с мемоизацией."""
        if n < 2:
            return n
        return fibonacci(n-1) + fibonacci(n-2)
    
    # Без @lru_cache вызов fibonacci(35) был бы крайне медленным.
    # С декоратором результат для каждого 'n' вычисляется один раз.
    print(fibonacci(35))  # Быстро
  3. Валидация аргументов и проверка прав доступа в веб-приложении (на примере Flask-like подхода):

    def require_auth(role: str = "user"):
        """Декоратор для проверки авторизации и роли пользователя."""
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                # Здесь была бы реальная проверка токена или сессии
                current_user = get_current_user_from_request()
                if not current_user or current_user.role != role:
                    raise PermissionError(f"Access denied. Required role: {role}")
                return func(*args, **kwargs)
            return wrapper
        return decorator
    
    @require_auth(role="admin")
    def delete_user(user_id: int):
        """Только администратор может удалять пользователей."""
        # ... логика удаления ...
        return f"User {user_id} deleted"
  4. Регистрация обработчиков (паттерн Plugin):

    _PROCESSORS = {}
    
    def register_processor(name: str):
        """Декоратор для регистрации функций-обработчиков в словаре."""
        def decorator(func):
            _PROCESSORS[name] = func
            return func
        return decorator
    
    @register_processor("json")
    def parse_json(data: str):
        import json
        return json.loads(data)
    
    @register_processor("csv")
    def parse_csv(data: str):
        import csv
        # ... парсинг CSV ...
        return rows
    
    # Теперь можно динамически выбирать обработчик
    def process(data: str, format_type: str):
        processor = _PROCESSORS.get(format_type)
        if processor:
            return processor(data)
        raise ValueError(f"Unsupported format: {format_type}")

Ключевое преимущество декораторов — они позволяют отделить сквозную функциональность (cross-cutting concerns), такую как логирование, кеширование, безопасность, от основной бизнес-логики, делая код чище и более поддерживаемым.