Декораторы в Python

8535b204182816f7a9fb39a0f4d5cc79.jpg

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

Синтаксис

Вот пример того, как выглядит структура написания собственного декоратора:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        # Дополнительный функционал перед вызовом функции 
        result = func(*args, **kwargs)  
        # Вызов декорируемой функции с переданными аргументами
        # Дополнительный функционал после вызова функции
        return result
    return wrapper

Благодаря передаче аргументов *args и **kwargs декораторы могут быть применены к функциям с различным количеством аргументов и именованными аргументами.

Для написания декоратора для класса Python вы можете использовать аналогичный подход, что и для функций, только вместо функций в качестве аргумента ваш декоратор будет принимать классы. Вот пример:

def class_decorator(cls):
    class WrappedClass(cls):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # Дополнительный функционал при инициализации класса (по желанию)
            print("Дополнительный функционал при инициализации класса")

    return WrappedClass

В этом примере class_decorator — это функция-декоратор, которая принимает класс cls в качестве аргумента. Внутри нее определен вложенный класс WrappedClass, который наследуется от декорируемого класса cls. При этом переопределен метод __init__, чтобы добавить дополнительный функционал при инициализации класса. Наконец, возвращается новый класс WrappedClass, который уже имеет дополнительный функционал, заданный декоратором.

Применить этот декоратор к классу можно таким образом:

@class_decorator
class MyClass:
    def __init__(self, x):
        self.x = x

В этом примере класс MyClass декорируется с помощью class_decorator, который добавляет дополнительный функционал при инициализации класса.

Пример

Давайте создадим простой декоратор, который будет выводить сообщение до и после вызова функции:

def simple_decorator(func):
    def wrapper():
        print("До вызова функции")
        func()
        print("После вызова функции")
    return wrapper

@simple_decorator
def hello():
    print("Привет, мир!")
hello()

#До вызова функции
#Привет, мир!
#После вызова функции

В этом примере simple_decorator — это декоратор. Он принимает функцию func в качестве аргумента, создает новую функцию wrapper, которая сначала выводит «До вызова функции», затем вызывает функцию func и, наконец, выводит «После вызова функции». После этого он возвращает wrapper. При помощи синтаксиса @simple_decorator мы применяем декоратор к функции hello.

Зачем это нужно

Декораторы используются для различных целей, таких как:

  1. Логирование: Регистрация действий или ошибок в приложении.

  2. Аутентификация и авторизация: Проверка доступа к определенным функциям или ресурсам.

  3. Кеширование: Сохранение результатов выполнения функции для повторного использования.

  4. Валидация аргументов: Проверка корректности переданных аргументов функции.

  5. Изменение поведения функции: Добавление дополнительного функционала перед или после выполнения функции.

Использование нескольких декораторов для одной функции

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

Например, если у вас есть функция my_function и вы применяете к ней два декоратора decorator1 и decorator2, то порядок применения будет следующим:

@decorator1
@decorator2
def my_function():
    pass

Сначала будет применен decorator2, а затем decorator1. То есть my_function сначала будет обернута в decorator2, а затем результат этого обертывания будет передан в decorator1.

Внутренняя функция

В контексте декораторов внутренняя функция — это функция, которая определяется внутри другой функции (или декоратора). Она используется для оборачивания или расширения функциональности другой функции.

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

Вот пример:

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

@my_decorator
def hello():
    print("Привет, мир!")

hello()
# Дополнительный функционал перед вызовом функции
# Привет, мир!
# Дополнительный функционал после вызова функции

Здесь внутренняя функция wrapper определена внутри декоратора my_decorator. Она оборачивает вызов функции hello, позволяя добавить дополнительный функционал до и после выполнения hello. Внутренняя функция wrapper принимает те же аргументы, что и декорируемая функция hello, и передает их дальше для ее исполнения.

Использование внутренних функций в декораторах позволяет динамически изменять поведение функций или классов без изменения их исходного кода.

Как сохранить метаданные о декорируемой функции при использовании декораторов

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

Давайте разберем основные метаданные функции в Python:

1.      Имя функции: Это имя, с которым функция была определена. Вы можете получить его, обращаясь к атрибуту __name__.

2.      Документация (docstring): Это строка документации, которая содержит описание функции. Она обычно записывается в тройных кавычках сразу после заголовка функции. Вы можете получить доступ к документации через атрибут __doc__.

3.      Аргументы: Это информация о параметрах функции, таких как их имена и значения по умолчанию. Вы можете получить доступ к этим аргументам с помощью модуля inspect.

4.      Аннотации типов: Это типы аргументов и возвращаемого значения функции. Они записываются в виде аннотаций типов после объявления параметров функции и перед знаком > для возвращаемого значения. Аннотации типов не являются строго обязательными, но могут улучшить читаемость и поддерживаемость кода.

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

Для того чтобы сохранить метаданные о декорируемой функции при использовании декораторов, вы можете воспользоваться инструментами из стандартной библиотеки Python, такими как модуль functools и его декоратор functools.wraps. Этот декоратор позволяет скопировать метаданные (такие как имя функции, документация и атрибуты) из оригинальной функции в обернутую функцию, создаваемую декоратором. Вот пример:

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # Ваш код декоратора
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def example_function():
    """Пример декорируемой функции."""
    pass

print(example_function.__name__)        # Вывод: example_function
print(example_function.__doc__)         # Вывод: Пример декорируемой функции.

В этом примере @wraps (func) перед определением функции wrapper указывает, что метаданные оригинальной функции func должны быть скопированы в функцию wrapper. Таким образом, обернутая функция wrapper будет иметь такие же метаданные, как и оригинальная функция func, что позволяет сохранить информацию о имени функции, документации и других атрибутах.

Задание

Написать декоратор debug, который будет выводить на экран информацию о вызове декорируемой функции, включая ее имя, переданные аргументы и возвращаемое значение.

Решение

from functools import wraps
import inspect

def func_info(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Вызвана функция: {func.__name__}")
        print(f"Получены аргументы: {inspect.signature(func)}")
        result = func(*args, **kwargs)  # Вызов декорируемой функции с переданными аргументами
        print(f"Возвращаемое значение: {result}")
        return result
    return wrapper

@func_info
def hello(a=5):
    return a

print(hello())
# Вызвана функция: hello
# Получены аргументы: (a=5)
# Возвращаемое значение: 5
# 5

Мы рассмотрели основные концепции декораторов, включая их синтаксис, применение к функциям и классам, сохранение метаданных и создание собственных декораторов. Надеюсь, данная информация пригодится для создания декораторов в ваших проектах Python.

© Habrahabr.ru