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