Введение в декораторы на Python

Spread the love

Декоратор – это шаблон проектирования, который мы можем использовать для добавления новых функциональных возможностей к уже существующей функции без необходимости изменять ее структуру. Декоратор должен вызываться непосредственно перед функцией, которая должна быть расширена. С помощью декораторов вы можете динамически изменять функциональные возможности методов, функции или класса без непосредственного использования наследования. Это хороший, способ расширения функциональности функции, которую не хотите изменить напрямую.

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


Spread the love

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *