Введение в декораторы на Python
Декоратор — это шаблон проектирования, который мы можем использовать для добавления новых функциональных возможностей к уже существующей функции без необходимости изменять ее структуру. Декоратор должен вызываться непосредственно перед функцией, которая должна быть расширена. С помощью декораторов вы можете динамически изменять функциональные возможности методов, функции или класса без непосредственного использования наследования. Это хороший, способ расширения функциональности функции, которую не хотите изменить напрямую.
В этой статье мы подробно обсудим декораторы в Python.
Как создавать декораторы
Давайте посмотрим, как декораторы могут быть созданы. В качестве примера мы создадим декоратор, который будет преобразовывать строки в нижний регистр. Для этого нам нужно создать функцию декоратора, и в ней нужно определить функцию оболочку, в нашем случае wrapper. Посмотрите на следующий код:
def lowercase(func): def wrapper(): func_ret = func() change_to_lowercase = func_ret.lower() return change_to_lowercase return wrapper
В приведенном выше сценарии мы создали декоратор с именем lowercase, который принимает функцию в качестве аргумента. Чтобы опробовать нашу функцию lowercase, нам нужно создать новую функцию и затем передать ее этому декоратору. Обратите внимание, что поскольку функции в Python являются first-class, вы можете назначить функцию переменной или рассматривать ее как единое целое. Мы будем использовать этот трюк для вызова функции декоратора:
def hello_function(): return 'HELLO WORLD' decorate = lowercase(hello_function) print(decorate())
Output
hello world
Обратите внимание, что вы можете объединить вышеупомянутые две части кода в один. Мы создали функцию hello_function(), которая возвращает предложение «HELLO WORLD». Затем мы вызвали декоратор и передали имя этой функции в качестве аргумента, присваивая его переменной decorate. После выполнения можно увидеть, что полученное предложение было преобразовано в нижний регистр.
Тем не менее, есть более простой способ применения декораторов в Python. Можно просто добавить символ @ перед именем функции декоратора чуть выше декорируемой функции. Например:
@lowercase def hello_function(): return 'HELLO WORLD' print(hello_function())
Output
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())
Output
['hello', 'world']
Наше предложение было разделено на две части и преобразовано в строчные буквы, так как мы применили к hello_function декораторы lowercase и split_sentence.
Передача аргументов функциям декоратора
Декораторы 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"))
Output
The arguments are: Nicholas, Samuel Your first and second names are Nicholas and Samuel respectively
В приведенном выше сценарии декоратор принимает два аргумента :, argument1 и argument2.
Создание декораторов общего назначения
Декораторы общего назначения могут быть применены к любой функции. Такого рода декораторы очень полезны, например, для отладки.
Мы можем определить их, используя аргументы *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()
Output
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)
Output
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")
Output
Positional arguments: () Keyword arguments: {'secondName': 'Samuel', 'firstName': 'Nicholas'} Passing keyword arguments
Два именных аргумента были переданы декоратору.
Как отлаживать декораторы
В этот момент вы, наверное, видели, что мы используем декораторы для обертывания вокруг функции. Обертывание функции это по сути замыкание. Замыкание скрывает исходное имя функции, список ее параметров и строку документации docstring.
Например: если мы попытаемся получить метаданные для декоратора function_with_arguments, мы получим метаданные замыкания (то есть функции обертки). Давайте продемонстрируем это:
function_with_arguments.__name__
Output
'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())
Output
hello world
Поскольку мы использовали functools.wraps в функции-обертке, мы можем проверить метаданные функции на «hello_function»:
hello_function.__name__
Output
'hello_function'
hello_function.__doc__
Output
'Saying hello'
Приведенный выше скрипт ясно показывает, что метаданные теперь ссылаются на функцию, а не на оболочку. Я рекомендую вам всегда использовать functools.wraps каждый раз, когда вы определяете декоратор. Это намного упростит отладку, в случае необходимости.
Заключение
Цель декораторов — динамически изменять функциональные возможности класса, метода или функции без непосредственного использования наследования или изменения исходного кода класса, метода или функции, которую нам нужно декорировать. В этой статье мы увидели, как создавать простые и универсальные декораторы и как передавать аргументы декораторам. Мы также увидели, как отлаживать декораторы во время разработки с помощью модуля functools.
Оригинал: Introduction to Python Decorators