- Home »

Декораторы в 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 или выделенный сервер — так удобнее экспериментировать с настройками без риска сломать основную систему.
Полезные ссылки:
Декораторы — это не просто синтаксический сахар, это философия написания кода. Один раз освоив их, ты будешь использовать постоянно. Начни с простых примеров, а потом переходи к более сложным кейсам. Удачи в разработке!
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.