В этой статье мы поговорим о декораторах в 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.
Зачем это нужно
Декораторы используются для различных целей, таких как:
Логирование: Регистрация действий или ошибок в приложении.
Аутентификация и авторизация: Проверка доступа к определенным функциям или ресурсам.
Кеширование: Сохранение результатов выполнения функции для повторного использования.
Валидация аргументов: Проверка корректности переданных аргументов функции.
Изменение поведения функции: Добавление дополнительного функционала перед или после выполнения функции.
Использование нескольких декораторов для одной функции
Можно использовать несколько декораторов для одной функции в 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:
Имя функции: Это имя, с которым функция была определена. Вы можете получить его, обращаясь к атрибуту __name__.
Документация (docstring): Это строка документации, которая содержит описание функции. Она обычно записывается в тройных кавычках сразу после заголовка функции. Вы можете получить доступ к документации через атрибут __doc__.
Аргументы: Это информация о параметрах функции, таких как их имена и значения по умолчанию. Вы можете получить доступ к этим аргументам с помощью модуля inspect.
Аннотации типов: Это типы аргументов и возвращаемого значения функции. Они записываются в виде аннотаций типов после объявления параметров функции и перед знаком > для возвращаемого значения. Аннотации типов не являются строго обязательными, но могут улучшить читаемость и поддерживаемость кода.
Метаданные полезны для документирования и понимания кода, а также для создания инструментов разработки, которые могут автоматически анализировать и использовать информацию о функциях и классах. В случае использования декораторов, сохранение метаданных о декорируемой функции позволяет сохранить эту информацию, что обеспечивает согласованное поведение и улучшает отладку и понимание кода.
Для того чтобы сохранить метаданные о декорируемой функции при использовании декораторов, вы можете воспользоваться инструментами из стандартной библиотеки 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.