Что такое декораторы в Python
Если вы писали на Python больше недели, вы наверняка видели символ @ над функцией. Это декоратор — мощный и понятный инструмент программирования.
Декораторы в Python позволяют "оборачивать" одну функцию в другую. Они изменяют или расширяют поведение функции без изменения исходного кода. Декораторы используются повсеместно: в Flask, Django, FastAPI, pytest, и даже в стандартной библиотеке Python.
Принцип работы декораторов
Основы декораторов
Декоратор представляет собой функцию, которая принимает другую функцию и возвращает новую функцию или модифицированную старую.
def decorator(func):
def wrapper():
print("До вызова")
func()
print("После вызова")
return wrapper
@decorator
def say_hello():
print("Привет!")
say_hello()
Результат выполнения:
До вызова
Привет!
После вызова
Символ @decorator является синтаксическим сахаром для записи:
say_hello = decorator(say_hello)
Внутреннее устройство декораторов
Чтобы понять декораторы в Python, необходимо знать ключевые концепции:
- В Python функции являются объектами первого класса
- Функции можно передавать как аргументы другим функциям
- Внутри функций можно определять другие функции
- Существует механизм замыканий (closure)
Универсальный шаблон декоратора
Минимальный шаблон декоратора выглядит следующим образом:
def decorator(func):
def wrapper(*args, **kwargs):
# Действия до выполнения функции
result = func(*args, **kwargs)
# Действия после выполнения функции
return result
return wrapper
Практические примеры декораторов
Декоратор измерения времени выполнения
import time
def timing(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
execution_time = time.time() - start
print(f"{func.__name__} заняло {execution_time:.4f} сек")
return result
return wrapper
@timing
def slow_func():
time.sleep(1)
return "Операция завершена"
slow_func()
Данный декоратор полезен для профилирования производительности функций и оптимизации кода.
Декоратор проверки авторизации
def require_admin(func):
def wrapper(user, *args, **kwargs):
if user != "admin":
raise PermissionError("Доступ запрещён")
return func(user, *args, **kwargs)
return wrapper
@require_admin
def delete_database(user):
print("База данных удалена!")
return True
delete_database("admin") # Успешное выполнение
Такой декоратор обеспечивает безопасность приложения, контролируя доступ к критически важным функциям.
Декоратор логирования
import logging
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"Вызов функции {func.__name__} с аргументами {args}, {kwargs}")
try:
result = func(*args, **kwargs)
logging.info(f"Функция {func.__name__} выполнена успешно")
return result
except Exception as e:
logging.error(f"Ошибка в функции {func.__name__}: {e}")
raise
return wrapper
Декораторы с аргументами
Создание фабрики декораторов
Когда декоратор должен принимать параметры, создается фабрика декораторов:
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
results = []
for _ in range(n):
result = func(*args, **kwargs)
results.append(result)
return results
return wrapper
return decorator
@repeat(3)
def greet():
print("Привет!")
return "Приветствие"
greet()
Декоратор с условными параметрами
def cache_result(ttl_seconds=300):
def decorator(func):
cache = {}
def wrapper(*args, **kwargs):
key = str(args) + str(kwargs)
if key in cache:
cached_time, cached_result = cache[key]
if time.time() - cached_time < ttl_seconds:
return cached_result
result = func(*args, **kwargs)
cache[key] = (time.time(), result)
return result
return wrapper
return decorator
Встроенные декораторы Python
Основные встроенные декораторы
Python предоставляет несколько встроенных декораторов для работы с классами и функциями:
- @staticmethod — создает статический метод класса, который не требует экземпляра
- @classmethod — создает метод класса, принимающий класс как первый аргумент cls
- @property — превращает метод в атрибут с возможностью геттера, сеттера и делетера
- @functools.lru_cache — кэширует результаты функции для повышения производительности
Пример использования @property
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Температура не может быть ниже абсолютного нуля")
self._celsius = value
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
Сохранение метаданных функций
Использование functools.wraps
Для сохранения имени функции, документации и других атрибутов оригинальной функции необходимо использовать декоратор @functools.wraps:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Вызывается функция: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@my_decorator
def important_function():
"""Это важная функция."""
return "Результат"
print(important_function.__name__) # important_function
print(important_function.__doc__) # Это важная функция.
Без @wraps имя функции стало бы wrapper, а документация потерялась.
Применение декораторов в популярных фреймворках
Django
В Django декораторы широко используются для контроля доступа и обработки HTTP-запросов:
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
@login_required
@require_http_methods(["GET", "POST"])
def dashboard(request):
return render(request, 'dashboard.html')
Flask
Flask использует декораторы для маршрутизации и обработки запросов:
from flask import Flask
app = Flask(__name__)
@app.route('/home')
@app.route('/dashboard')
def home():
return "Добро пожаловать!"
@app.before_request
def before_request():
print("Выполняется перед каждым запросом")
pytest
В pytest декораторы применяются для параметризации тестов и настройки тестовой среды:
import pytest
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(2, 3, 5),
(10, 5, 15)
])
def test_sum(a, b, expected):
assert a + b == expected
@pytest.fixture
def sample_data():
return {"key": "value"}
Использование нескольких декораторов
Порядок применения декораторов
При использовании нескольких декораторов они применяются снизу вверх:
@decorator_a
@decorator_b
@decorator_c
def func():
pass
Эквивалентно записи:
func = decorator_a(decorator_b(decorator_c(func)))
Практический пример множественных декораторов
def bold(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return f"<b>{result}</b>"
return wrapper
def italic(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return f"<i>{result}</i>"
return wrapper
@bold
@italic
def say_hello():
return "Привет!"
print(say_hello()) # <b><i>Привет!</i></b>
Рекомендации по использованию декораторов
Лучшие практики
При работе с декораторами следует придерживаться следующих принципов:
- Всегда используйте @functools.wraps для сохранения метаданных функции
- Применяйте *args и **kwargs для универсальности декоратора
- Избегайте создания излишне сложных вложенных декораторов
- Не злоупотребляйте декораторами с побочными эффектами
- Помните, что декораторы применяются во время импорта модуля, а не при вызове функции
Тестирование функций с декораторами
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Декоратор сработал")
return func(*args, **kwargs)
return wrapper
@my_decorator
def calculate(x, y):
return x + y
# Для тестирования оригинальной функции
original_calculate = calculate.__wrapped__
assert original_calculate(2, 3) == 5
Распространенные ошибки и их решения
Основные ошибки при работе с декораторами
Проблема потери метаданных функции
- Решение: использование @functools.wraps
Неправильная обработка аргументов
- Решение: применение *args и **kwargs в wrapper-функции
Ошибки в декораторах с аргументами
- Решение: создание правильной фабрики декораторов с тремя уровнями вложенности
Проблемы с производительностью
- Решение: кэширование результатов и избежание избыточных вычислений в декораторе
Отладка декорированных функций
import functools
def debug_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"DEBUG: Вызов {func.__name__} с args={args}, kwargs={kwargs}")
try:
result = func(*args, **kwargs)
print(f"DEBUG: {func.__name__} вернула {result}")
return result
except Exception as e:
print(f"DEBUG: {func.__name__} вызвала исключение {e}")
raise
return wrapper
Заключение
Декораторы в Python представляют собой мощный инструмент для расширения функциональности без изменения исходного кода функций. Они широко применяются в современной разработке для решения задач логирования, кэширования, авторизации, измерения производительности и многих других.
Правильное использование декораторов делает код более чистым, модульным и переиспользуемым. Освоив принципы создания и применения декораторов, разработчик получает доступ к элегантным решениям множества повседневных задач программирования.
Часто задаваемые вопросы
Что представляет собой декоратор в Python? Декоратор — это функция, которая принимает другую функцию в качестве аргумента и возвращает модифицированную версию этой функции, расширяя или изменяя её поведение.
Как работает символ @ в Python? Символ @ представляет синтаксический сахар для применения декоратора. Запись @decorator эквивалентна func = decorator(func).
Можно ли передавать аргументы декоратору? Да, для этого создается фабрика декораторов — функция, которая принимает аргументы и возвращает декоратор: @decorator(arg).
Зачем нужен @functools.wraps? Декоратор @functools.wraps сохраняет имя, документацию и другие атрибуты оригинальной функции, предотвращая их потерю при декорировании.
Относятся ли декораторы к функциональному программированию? Да, декораторы являются частью функционального стиля программирования в Python, поскольку они работают с функциями как с объектами первого класса.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов