Для чего нужен декоратор?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего нужен декоратор
Декоратор — это функция, которая оборачивает другую функцию или класс, добавляя к ней новую функциональность, не изменяя исходный код. Это паттерн проектирования, который делает код чище и более переиспользуемым.
Основной концепт
Декоратор — это функция, которая принимает функцию, оборачивает её и возвращает новую функцию с расширенной функциональностью.
Оригинальная функция
↓
Декоратор
↓
Расширенная функция
Простейший пример
# Декоратор — это просто функция
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(',')))
Когда использовать декораторы
- Повторяющаяся логика — логирование, проверка прав, кеширование
- Кроссcuttingoncerns — функциональность, которая пересекает несколько компонентов
- Разделение ответственности — отделяем бизнес-логику от инфраструктуры
- DRY принцип — не дублируем код, используем декораторы
Вывод
Декораторы — это мощный инструмент для расширения функциональности без изменения исходного кода. Они делают код чище, более модульным и переиспользуемым. Используйте их для повторяющейся функциональности: логирование, кеширование, авторизация, валидация.