Home » Декораторы в Python — что это и как их использовать
Декораторы в Python — что это и как их использовать

Декораторы в Python — что это и как их использовать

Декораторы в Python — это одна из тех крутых фич, которые делают код элегантным и мощным одновременно. Если ты настраиваешь сервера, пишешь скрипты для мониторинга или автоматизации, то декораторы станут твоим лучшим другом. Они позволяют “обернуть” функцию в дополнительную логику без изменения её основного кода — логирование, кеширование, аутентификация, замер времени выполнения. Всё это можно элегантно реализовать через декораторы.

По сути, декораторы решают проблему дублирования кода. Представь: у тебя есть 20 функций для работы с API, и каждой нужна проверка токена. Вместо того чтобы копипастить одну и ту же логику, ты просто навешиваешь декоратор @auth_required. Чисто, понятно, поддерживаемо.

Как это работает под капотом

Декораторы в Python — это синтаксический сахар для функций высшего порядка. В реальности конструкция @decorator над функцией эквивалентна вызову func = decorator(func). Декоратор принимает функцию как аргумент и возвращает модифицированную версию.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Что-то происходит до вызова функции")
        result = func(*args, **kwargs)
        print("Что-то происходит после вызова функции")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Привет, {name}!")

# Эквивалентно:
# say_hello = my_decorator(say_hello)

Ключевой момент: wrapper-функция получает все аргументы оригинальной функции через *args и **kwargs, что делает декоратор универсальным.

Быстрый старт: создаём полезные декораторы

Начнём с практических примеров, которые пригодятся в серверной разработке:

Декоратор для замера времени выполнения

import time
from functools import wraps

def timing_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} выполнилась за {end_time - start_time:.4f} секунд")
        return result
    return wrapper

@timing_decorator
def heavy_computation():
    time.sleep(2)
    return "Готово"

Декоратор для retry логики

import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise e
                    print(f"Попытка {attempt + 1} неудачна: {e}")
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_api_call():
    # Имитация нестабильного API
    import random
    if random.random() < 0.7:
        raise Exception("API недоступен")
    return "Данные получены"

Практические кейсы для серверной разработки

Задача Без декоратора С декоратором Преимущества
Логирование Добавлять logging.info() в каждую функцию @log_calls Централизованное логирование, легко включить/выключить
Кеширование Проверять cache перед каждым вызовом @cache или @lru_cache Прозрачное кеширование, автоматическое управление
Аутентификация Проверять токены в каждой функции @auth_required Единообразная проверка прав доступа
Rate limiting Счётчики запросов в каждой функции @rate_limit(calls=100, period=60) Гибкое управление нагрузкой

Продвинутые техники

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

def cache_with_ttl(ttl_seconds=300):
    def decorator(func):
        cache = {}
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = str(args) + str(kwargs)
            current_time = time.time()
            
            if key in cache:
                result, timestamp = cache[key]
                if current_time - timestamp < ttl_seconds:
                    print(f"Данные из кеша для {func.__name__}")
                    return result
                else:
                    del cache[key]
            
            result = func(*args, **kwargs)
            cache[key] = (result, current_time)
            return result
        return wrapper
    return decorator

@cache_with_ttl(ttl_seconds=60)
def get_server_stats():
    # Эмуляция тяжёлого запроса
    time.sleep(1)
    return {"cpu": 45, "memory": 78, "disk": 23}

Декоратор для мониторинга системных ресурсов

import psutil
from functools import wraps

def monitor_resources(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        process = psutil.Process()
        
        # Замеряем ресурсы до выполнения
        cpu_before = process.cpu_percent()
        memory_before = process.memory_info().rss / 1024 / 1024  # MB
        
        result = func(*args, **kwargs)
        
        # Замеряем ресурсы после выполнения
        cpu_after = process.cpu_percent()
        memory_after = process.memory_info().rss / 1024 / 1024  # MB
        
        print(f"Функция {func.__name__}:")
        print(f"  CPU: {cpu_after:.2f}% (изменение: {cpu_after - cpu_before:.2f}%)")
        print(f"  Memory: {memory_after:.2f}MB (изменение: {memory_after - memory_before:.2f}MB)")
        
        return result
    return wrapper

Встроенные декораторы и сторонние решения

Python предлагает несколько встроенных декораторов:

  • @functools.lru_cache — встроенное кеширование с LRU-политикой
  • @functools.wraps — сохраняет метаданные оригинальной функции
  • @property — превращает методы в свойства
  • @staticmethod и @classmethod — для методов класса

Популярные сторонние решения:

  • tenacity — продвинутая retry-логика
  • cachetools — различные стратегии кеширования
  • click — декораторы для CLI-приложений
  • flask/fastapi — роутинг через декораторы

Пример с tenacity:

pip install tenacity

from tenacity import retry, stop_after_attempt, wait_fixed

@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
def connect_to_database():
    # Логика подключения к БД
    pass

Нестандартные способы использования

Декоратор для автоматического создания backup'ов

import shutil
import os
from datetime import datetime

def auto_backup(backup_dir="/backup"):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if not os.path.exists(backup_dir):
                os.makedirs(backup_dir)
            
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_name = f"{func.__name__}_backup_{timestamp}"
            
            # Создаём backup перед выполнением
            print(f"Создаём backup: {backup_name}")
            
            result = func(*args, **kwargs)
            
            # Логика создания backup'а зависит от конкретной задачи
            return result
        return wrapper
    return decorator

Декоратор для уведомлений в Slack/Telegram

import requests
from functools import wraps

def notify_on_failure(webhook_url):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                message = f"🚨 Функция {func.__name__} упала с ошибкой: {str(e)}"
                requests.post(webhook_url, json={"text": message})
                raise e
        return wrapper
    return decorator

Интеграция с популярными инструментами

Декораторы + Docker

import docker
from functools import wraps

def with_docker_container(image="python:3.9"):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            client = docker.from_env()
            container = client.containers.run(
                image,
                command="python -c 'print(\"Container ready\")'",
                detach=True
            )
            
            try:
                result = func(*args, **kwargs)
                return result
            finally:
                container.stop()
                container.remove()
        return wrapper
    return decorator

Декораторы + Prometheus метрики

from prometheus_client import Counter, Histogram, start_http_server
import time

# Метрики
function_calls = Counter('function_calls_total', 'Total function calls', ['function_name'])
function_duration = Histogram('function_duration_seconds', 'Function duration', ['function_name'])

def prometheus_metrics(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        function_calls.labels(function_name=func.__name__).inc()
        
        start_time = time.time()
        result = func(*args, **kwargs)
        duration = time.time() - start_time
        
        function_duration.labels(function_name=func.__name__).observe(duration)
        return result
    return wrapper

Автоматизация и скрипты

Декораторы открывают массу возможностей для автоматизации:

  • Планировщик задач — декоратор @schedule для периодического выполнения
  • Автоматическое логирование — все вызовы функций логируются без изменения кода
  • Мониторинг производительности — сбор метрик в фоновом режиме
  • Graceful shutdown — автоматическая обработка сигналов системы

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

#!/usr/bin/env python3
import time
import psutil
from functools import wraps

def scheduled_task(interval=60):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            while True:
                try:
                    func(*args, **kwargs)
                except Exception as e:
                    print(f"Ошибка в {func.__name__}: {e}")
                time.sleep(interval)
        return wrapper
    return decorator

@scheduled_task(interval=30)
def check_server_health():
    cpu = psutil.cpu_percent()
    memory = psutil.virtual_memory().percent
    disk = psutil.disk_usage('/').percent
    
    print(f"CPU: {cpu}%, Memory: {memory}%, Disk: {disk}%")
    
    if cpu > 80 or memory > 80 or disk > 90:
        print("⚠️  Высокая нагрузка на сервер!")

if __name__ == "__main__":
    check_server_health()

Подводные камни и best practices

Основные ошибки:

  • Не используешь @wraps — теряются метаданные функции
  • Не обрабатываешь исключения — декоратор может скрыть ошибки
  • Проблемы с производительностью — слишком тяжёлые операции в декораторе
  • Порядок декораторов имеет значение — они применяются снизу вверх

Правильный подход:

from functools import wraps
import logging

def robust_decorator(func):
    @wraps(func)  # Сохраняем метаданные
    def wrapper(*args, **kwargs):
        try:
            # Лёгкие операции до вызова
            logging.info(f"Вызов {func.__name__}")
            result = func(*args, **kwargs)
            # Лёгкие операции после вызова
            logging.info(f"Завершён {func.__name__}")
            return result
        except Exception as e:
            logging.error(f"Ошибка в {func.__name__}: {e}")
            raise  # Не глушим исключение
    return wrapper

Заключение и рекомендации

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

Используй декораторы когда:

  • Нужно добавить одинаковую логику к множеству функций
  • Требуется мониторинг, логирование или профилирование
  • Необходимо кеширование или retry-логика
  • Хочешь разделить бизнес-логику и вспомогательные операции

Не используй декораторы когда:

  • Логика слишком специфична для конкретной функции
  • Декоратор получается слишком сложным
  • Есть проблемы с производительностью

Для разработки и тестирования декораторов рекомендую поднять VPS или выделенный сервер — так удобнее экспериментировать с настройками без риска сломать основную систему.

Полезные ссылки:

Декораторы — это не просто синтаксический сахар, это философия написания кода. Один раз освоив их, ты будешь использовать постоянно. Начни с простых примеров, а потом переходи к более сложным кейсам. Удачи в разработке!


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

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

Leave a reply

Your email address will not be published. Required fields are marked