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

от автора

В этой статье мы поговорим о декораторах в 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.


ссылка на оригинал статьи https://habr.com/ru/articles/814217/