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

Для чего нужен декоратор?

1.2 Junior🔥 181 комментариев
#Python Core#Архитектура и паттерны

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

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

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

Для чего нужен декоратор

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

Основной концепт

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

Оригинальная функция
       ↓
    Декоратор
       ↓
Расширенная функция

Простейший пример

# Декоратор — это просто функция
def my_decorator(func):
    """Простейший декоратор"""
    def wrapper(*args, **kwargs):
        print(f"Функция {func.__name__} начинает работу")
        result = func(*args, **kwargs)  # Вызываем оригинальную функцию
        print(f"Функция {func.__name__} завершила работу")
        return result
    return wrapper

# Применяем декоратор через @
@my_decorator
def say_hello(name: str):
    print(f"Привет, {name}!")
    return f"Ответ от {name}"

# Вызов:
result = say_hello("Иван")
print(result)

# Вывод:
# Функция say_hello начинает работу
# Привет, Иван!
# Функция say_hello завершила работу
# Ответ от Иван

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

1. Логирование

import functools
import logging
from datetime import datetime

logger = logging.getLogger(__name__)

def log_calls(func):
    """Декоратор для логирования вызовов функции"""
    @functools.wraps(func)  # Сохраняет метаданные оригинальной функции
    def wrapper(*args, **kwargs):
        logger.info(f"Вызов {func.__name__} с args={args}, kwargs={kwargs}")
        try:
            result = func(*args, **kwargs)
            logger.info(f"Функция {func.__name__} вернула {result}")
            return result
        except Exception as e:
            logger.error(f"Ошибка в {func.__name__}: {e}")
            raise
    return wrapper

@log_calls
def calculate(a: int, b: int) -> int:
    return a + b

calculate(5, 3)  # Логирует вызов и результат

2. Кеширование результатов

import functools
from typing import Any

def cache_result(func):
    """Кеш результатов функции"""
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Создаём ключ кеша на основе аргументов
        key = (args, tuple(sorted(kwargs.items())))
        
        if key in cache:
            print(f"Возврат результата из кеша для {key}")
            return cache[key]
        
        result = func(*args, **kwargs)
        cache[key] = result
        return result
    
    return wrapper

@cache_result
def fibonacci(n: int) -> int:
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))  # Вычисляется с кешированием

3. Проверка типов аргументов

import functools
from typing import get_type_hints

def validate_types(func):
    """Проверяет типы аргументов"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        hints = get_type_hints(func)
        
        # Проверяем позиционные аргументы
        arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
        for arg_name, arg_value in zip(arg_names, args):
            if arg_name in hints:
                expected_type = hints[arg_name]
                if not isinstance(arg_value, expected_type):
                    raise TypeError(
                        f"Аргумент {arg_name} должен быть {expected_type.__name__}, "
                        f"получено {type(arg_value).__name__}"
                    )
        
        return func(*args, **kwargs)
    return wrapper

@validate_types
def add(a: int, b: int) -> int:
    return a + b

add(5, 3)      # OK
add(5, "3")    # TypeError: Аргумент b должен быть int, получено str

4. Ограничение времени выполнения

import functools
import signal
import time

class TimeoutError(Exception):
    pass

def timeout(seconds: int):
    """Декоратор с параметром — ограничивает время выполнения"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            def handler(signum, frame):
                raise TimeoutError(f"Функция выполнялась более {seconds} секунд")
            
            # Устанавливаем обработчик сигнала (Unix only)
            signal.signal(signal.SIGALRM, handler)
            signal.alarm(seconds)
            
            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)  # Отменяем таймер
            
            return result
        return wrapper
    return decorator

@timeout(2)
def slow_function():
    time.sleep(3)  # Будет прерван по timeout

# slow_function()  # TimeoutError: Функция выполнялась более 2 секунд

5. Авторизация и аутентификация

import functools
from typing import Callable
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthCredentials

app = FastAPI()
security = HTTPBearer()

def require_auth(func: Callable) -> Callable:
    """Проверяет, что пользователь авторизован"""
    @functools.wraps(func)
    async def wrapper(*args, credentials: HTTPAuthCredentials = Depends(security), **kwargs):
        # Проверяем токен
        if not credentials or credentials.credentials != "valid-token-123":
            raise HTTPException(status_code=401, detail="Недействительный токен")
        
        return await func(*args, **kwargs)
    return wrapper

@app.get("/secure-endpoint")
@require_auth
async def secure_endpoint():
    return {"message": "Это защищённый endpoint"}

6. Измерение времени выполнения

import functools
import time

def measure_time(func):
    """Измеряет время выполнения функции"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"{func.__name__} выполнилась за {elapsed:.4f} сек")
        return result
    return wrapper

@measure_time
def process_data(items: int):
    time.sleep(0.1)
    return items * 2

process_data(100)  # process_data выполнилась за 0.1023 сек

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

import functools

def repeat(times: int):
    """Декоратор с параметром — повторяет функцию N раз"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat(times=3)
def greet(name: str):
    return f"Привет, {name}!"

print(greet("Мария"))
# ['Привет, Мария!', 'Привет, Мария!', 'Привет, Мария!']

Декораторы классов

import functools

def dataclass_like(cls):
    """Простейший декоратор класса — добавляет __repr__"""
    def __repr__(self):
        attrs = ', '.join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    
    cls.__repr__ = __repr__
    return cls

@dataclass_like
class User:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

user = User("Иван", 30)
print(user)  # User(name='Иван', age=30)

Стекирование декораторов

def uppercase(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

def add_exclamation(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result + "!"
    return wrapper

# Применяем несколько декораторов
@uppercase
@add_exclamation
def greet(name: str):
    return f"hello, {name}"

print(greet("мир"))  # HELLO, МИР!

# Порядок имеет значение:
# 1. Выполняется greet() → "hello, мир"
# 2. add_exclamation обрабатывает результат → "hello, мир!"
# 3. uppercase обрабатывает результат → "HELLO, МИР!"

Встроенные декораторы в Python

@property — атрибут как метод

class Circle:
    def __init__(self, radius: float):
        self._radius = radius
    
    @property
    def area(self) -> float:
        """Площадь вычисляется как свойство"""
        return 3.14 * self._radius ** 2

c = Circle(5)
print(c.area)  # 78.5 (вызов как атрибут, а не метод)

@staticmethod и @classmethod

class Math:
    @staticmethod
    def add(a, b):
        return a + b
    
    @classmethod
    def from_string(cls, value: str):
        return cls(*map(int, value.split(',')))

Когда использовать декораторы

  1. Повторяющаяся логика — логирование, проверка прав, кеширование
  2. Кроссcuttingoncerns — функциональность, которая пересекает несколько компонентов
  3. Разделение ответственности — отделяем бизнес-логику от инфраструктуры
  4. DRY принцип — не дублируем код, используем декораторы

Вывод

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