Декораторы в Python: что это такое, как работают и зачем нужны

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое декораторы в 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, поскольку они работают с функциями как с объектами первого класса.

Новости