Вступление
В Python декоратор - это шаблон проектирования, который мы можем использовать для добавления новых функций к уже существующему объекту без необходимости изменять его структуру. Декоратор следует вызывать непосредственно перед расширяемой функцией. С помощью декораторов вы можете динамически изменять функциональность метода, функции или класса без прямого использования подклассов. Это хорошая идея, если вы хотите расширить функциональность функции, которую не хотите напрямую изменять. Шаблоны декораторов могут быть реализованы везде, но Python предоставляет для этого более выразительный синтаксис и функции.
В этой статье мы подробно обсудим декораторы Python.
Как создавать декораторы
Давайте посмотрим, как в Python можно создавать декораторы. В качестве примера мы создадим декоратор, который мы можем использовать для преобразования выходной строки функции в нижний регистр. Для этого нам нужно создать функцию-декоратор и определить внутри нее оболочку. Взгляните на следующий сценарий:
def lowercase(func):
def wrapper():
func_ret = func()
change_to_lowercase = func_ret.lower()
return change_to_lowercase
return wrapper
В приведенном выше сценарии мы просто создали декоратор с именем в
lowercase
который принимает функцию в качестве аргумента. Чтобы
опробовать нашу lowercase
функцию, нам нужно создать новую функцию, а
затем передать ее этому декоратору. Обратите внимание: поскольку функции
в Python являются первоклассными, вы можете назначить функцию переменной
или рассматривать ее как единицу. Мы воспользуемся этим трюком, чтобы
вызвать функцию декоратора:
def hello_function():
return 'HELLO WORLD'
decorate = lowercase(hello_function)
print(decorate())
Выход
hello world
Обратите внимание, что вы можете объединить два вышеуказанных фрагмента
кода в один. Мы создали функцию hello_function()
которая возвращает
предложение «ПРИВЕТ, МИР». Затем мы вызвали декоратор и передали имя
этой функции в качестве аргумента, присвоив его переменной decorate.
После выполнения вы можете увидеть, что полученное предложение было
преобразовано в нижний регистр.
Однако в Python есть более простой способ применения декораторов. Мы
можем просто добавить @
перед именем функции-декоратора прямо над
функцией, которую нужно оформить. Например:
@lowercase
def hello_function():
return 'HELLO WORLD'
print(hello_function())
Выход
hello world
Как применить несколько декораторов к функции
Python позволяет нам применять более одного декоратора к одной функции. Чтобы сделать это правильно, убедитесь, что вы применяете декораторы в том же порядке, в котором вы запускали бы их как обычный код. Например, рассмотрим следующий декоратор:
def split_sentence(func):
def wrapper():
func_ret = func()
output = func_ret.split()
return output
return wrapper
Здесь мы создали декоратор, который берет входное предложение и
разбивает его на различные части. Декоратору было присвоено имя
split_sentence
. Теперь применим lowercase
и split_sentence
к
одной функции.
Чтобы выполнить эти операции в правильном порядке, примените их следующим образом:
@split_sentence
@lowercase
def hello_function():
return 'HELLO WORLD'
print(hello_function())
Выход
['hello', 'world']
Наше предложение было разделено на две части и преобразовано в нижний
регистр, так как мы применили декораторы lowercase
и split_sentence
hello_function
.
Передача аргументов функциям декоратора
Декораторы Python также могут перехватывать аргументы, которые передаются декорированным функциям. Аргументы, в свою очередь, будут переданы декорированной функции во время выполнения. Рассмотрим следующий пример:
def my_decorator(func):
def my_wrapper(argument1, argument2):
print("The arguments are: {0}, {1}".format(argument1, argument2))
func(argument1, argument2)
return my_wrapper
@my_decorator
def names(firstName, secondName):
print("Your first and second names are {0} and {1} respectively".format(firstName, secondName))
print(names("Nicholas", "Samuel"))
Выход
The arguments are: Nicholas, Samuel
Your first and second names are Nicholas and Samuel respectively
В приведенном выше сценарии декоратор принимает два аргумента:
argument1
и argument1
.
Создание декораторов общего назначения
Декораторы общего назначения могут применяться к любой функции. Такие декораторы очень полезны, например, для отладки.
Мы можем определить их, используя args
и **kwargs
. Все позиционные
аргументы и аргументы ключевого слова хранятся в этих двух переменных
соответственно. С помощью args
и kwargs
мы можем передавать любое
количество аргументов во время вызова функции. Например:
def my_decorator(func):
def my_wrapper(*args, **kwargs):
print('Positional arguments:', args)
print('Keyword arguments:', kwargs)
func(*args)
return my_wrapper
@my_decorator
def function_without_arguments():
print("No arguments")
function_without_arguments()
Выход
Positional arguments: ()
Keyword arguments: {}
No arguments
Как видите, декоратору не было передано никаких аргументов.
Теперь давайте посмотрим, как мы можем передавать значения позиционным аргументам:
@my_decorator
def function_with_arguments(x, y, z):
print(x, y, z)
function_with_arguments(5, 15, 25)
Выход
Positional arguments: (5, 15, 25)
Keyword arguments: {}
5 15 25
Мы передали декоратору три позиционных аргумента. Чтобы передать аргументы ключевого слова, мы должны использовать ключевые слова в вызове функции. Вот пример:
@my_decorator
def passing_keyword_arguments():
print("Passing keyword arguments")
passing_keyword_arguments(firstName="Nicholas", secondName="Samuel")
Выход
Positional arguments: ()
Keyword arguments: {'secondName': 'Samuel', 'firstName': 'Nicholas'}
Passing keyword arguments
В декоратор были переданы два аргумента ключевого слова.
В следующем разделе мы обсудим, как отлаживать декораторы.
Как отлаживать декораторы
На этом этапе вы, должно быть, видели, что мы используем декораторы для обертывания функций. Замыкание оболочки скрывает исходное имя функции, ее список параметров и строку документации.
Например: если мы попытаемся получить метаданные для декоратора
function_with_arguments
, мы получим метаданные закрытия оболочки.
Продемонстрируем это:
function_with_arguments.__name__
Выход
'my_wrapper'
Это представляет собой большую проблему во время отладки. Однако Python
предоставляет functools.wraps
который может помочь в решении этой
проблемы. Он работает путем копирования потерянных метаданных на вашу
декорированную крышку.
Теперь давайте продемонстрируем, как это работает:
import functools
def lowercase(func):
@functools.wraps(func)
def my_wrapper():
return func().lower()
return my_wrapper
@lowercase
def hello_function():
"Saying hello"
return 'HELLO WORLD'
print(hello_function())
Выход
hello world
Поскольку мы использовали functools.wraps
для функции-оболочки, мы
можем проверить метаданные функции на "hello_function":
hello_function.__name__
Выход
'hello_function'
hello_function.__doc__
Выход
'Saying hello'
Приведенный выше сценарий ясно показывает, что метаданные теперь
относятся к функции, а не к оболочке. Я рекомендую вам всегда
использовать functools.wraps
каждый раз, когда вы определяете
декоратор. Это значительно упростит вам отладку.
Заключение
Назначение декораторов - динамически изменять функциональность класса,
метода или функции без прямого использования подклассов или изменения
исходного кода класса, метода или функции, которые нам нужно украсить. В
этой статье мы увидели, как создавать простые декораторы общего
назначения и как передавать аргументы декораторам. Мы также увидели, как
отлаживать декораторы во время разработки с помощью модуля functools