[Python] Декораторы Python: хватит это терпеть

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
02-Июн-2021 13:33


Конец страданиям.Всем привет! В этой статье я расскажу об инструменте, разработанном мной, который изменяет работу декораторов в Python и делает их более «Питоничными».Я не буду рассказывать про области применения декораторов. Есть множество статей на эту тему.Для начала, давайте вспомним: что же такое декораторы в Пайтон.Если совсем просто, то это удобный способ передать одну функцию в другую и получить третью. В этом определении нет ни одного слова правды, но мы вернёмся к этому позже.Давайте разбираться!Как работают декораторы
def decorator_function(wrapped_func):
    def wrapper():
        print('Входим в функцию-обёртку')
        print('Оборачиваемая функция: ', wrapped_func)
        print('Выполняем обёрнутую функцию...')
        wrapped_func()
        print('Выходим из обёртки')
    return wrapper
Так выглядит функция-декоратор. Как вы можете увидеть, она принимает в качестве аргумента другую функцию. Затем с этой функцией что-то делают внутри вложенной функции-обёртки и возвращают из декоратора уже обёртку вместо исходной функции.Теперь можно декорировать:
@decorator_function
def hello_world():
        print('Hello world!')
hello_world()
Здесь декоратор получает функцию hello_world, и подменяет её своей вложенной функцией wrapper.Вывод:
Входим в функцию-обёртку
Оборачиваемая функция:  <function hello_world at 0x0201B2F8>
Выполняем обёрнутую функцию...
Hello world!
Выходим из обёртки
Важно помнить!Декоратор исполняется только один раз: при объявлении оборачиваемой функции. При дальнейшем вызове функции исполняется только вложенная функция wrapper.Мы это увидим, если добавим две строчки в наш декоратор:
def decorator_function(wrapped_func):
    print('Входим в декоратор')
    def wrapper():
        ...
    print('Выходим из декоратора')
    return wrapper
@decorator_function
def hello_world():
        print('Hello world!')
Входим в декоратор
Выходим из декоратора
hello_world()
Входим в функцию-обёртку
Оборачиваемая функция:  <function hello_world at 0x0201B2F8>
Выполняем обёрнутую функцию...
Hello world!
Выходим из обёртки
А вот и страдания: аргументы функции и аргументы декоратораУ функции, которую мы декорируем, могут быть аргументы. Принимает их вложенная функция wrapper:
def decorator_function(wrapped_func):
    def wrapper(*args):
        ...
        wrapped_func(args)
        ...
    return wrapper
@decorator_function
def hello_world(text):
        print(text)
hello_world('Hello world!')
А ещё, аргументы могут быть переданы непосредственно в декоратор:
def fictive(decorator_text):
    def decorator_function(wrapped_func):
        def wrapper(*args):
            print(decorator_text, end='')
            wrapped_func(*args)
        return wrapper
    return decorator_function
@fictive(decorator_text='Hello, ')
def hello_world(text):
        print(text)
hello_world('world!')
Здесь аргумент decorator_text передаётся при декорировании в строке №11 и попадает в функцию fictive, строка №1. Таким образом, появился ещё один уровень вложенности только для того, чтобы принять аргументы декоратора.Вывод:
Hello, world!
Пойдём дальше. А что, если декоратор может быть, в одних случаях с аргументами, в других - без аргументов? Поехали!
def fictive(_func=None, *, decorator_text=''):
    def decorator_function(wrapped_func):
        def wrapper(*args):
            print(decorator_text, end='')
            wrapped_func(*args)
        return wrapper
    if _func is None:
        return decorator_function
    else:
        return decorator_function(_func)
@fictive
def hello_world(text):
        print(text)
hello_world('Hello, world!')
@fictive(decorator_text='Hi, ')
def say(text):
        print(text)
say('world!')
Вывод:
Hello, world!
Hi, world!
Как Вам код? Вспомним, мантру Питонистов из начала статьи:
Декораторы - это удобный способ передать...
Ничего, на помощь придёт DecoratorHelper! Но, перед этим, ещё пара слов о декораторах.Мифы декораторов
  • Декораторы удобны. Думаю, с этим мы уже разобрались.
  • В декораторы нужно передавать функции. Передавать можно не только функции, но и любые callable объекты. Это такие объекты, у которых определён дандер метод (магический метод) __call__. Этот метод отвечает за операции, которые будут произведены при вызове объекта (когда вы ставите скобочки после имени объекта: object()). Вместо функции может быть метод или класс.
  • Декораторы - это функции. И опять: это может быть любой callable объект.
  • Декоратор возвращает функцию. Декоратор может возвращать что угодно. Стоит лишь помнить, что если декоратор возвращает не callable объект, то вызывать его не получится.
  • Передавать можно не только функции, но и аргументы. Вместо функции может быть метод или некоторые классы.
DecoratorHelper: решение проблемУстанавливаем модуль:
pip install DecoratorHelper
Импортируем и используем как декоратор:
from DecoratorHelper import DecoratorHelper
@DecoratorHelper
def hello_world(text):
        print(text)
hello_world('Hello, world!')
Что это даёт?
  • Вы больше не думаете над тем, будут ли аргументы у декоратора. DecoratorHelper думает об этом вместо Вас.
  • Вы получаете удобный, Питоничный доступ ко всем аргументам, самой функции, к тому, что будет происходить до и после выполнения функции.
В итоге Вы получаете вместо функции объект, который имеет следующие атрибуты:
  • self.function - оборачиваемая функция
  • self.decorator_args - аргументы декоратора. Кортеж позиционных аргументов, последний элемент которого - словарь с именованными аргументами.
  • self.function_args - аргументы функции. Кортеж позиционных аргументов, последний элемент которого - словарь с именованными аргументами.
  • self.pre_function - то, что будет происходить перед выполнением функции (так можно превратить функцию в коллбэк).
  • self.post_function - то, что будет происходить после выполнения функции (так можно добавить функции в коллбэк).
Как использовать?Перепишем приведённый ранее код декоратора, который может принимать/не принимать аргументы:
from DecoratorHelper import DecoratorHelper
def fictive(object):
    object.pre_function = lambda : print(*object.decorator_args[:-1], end='')
    return object
@fictive
@DecoratorHelper('Hello, ')
def hello_world(text):
        print(text)
hello_world('world!')
Как мы можем видеть, тело декоратора сократилось в 8 раз. Profit!Ограничение! Первым аргументом нельзя передавать callable объекты, иначе всё сломается :) Думаю, для большинства задач, это не смертельно...Что дальше?В следующих версиях планируется:
  • Улучшенная обработка аргументов.
  • Встроенный счётчик вызовов.
  • Возможность превратить объект в синглтон.
  • Возможность превратить объект в буилдер.
  • Может быть, возможность подключить асинхронность.И всё это в максимально удобном формате: singleton = True.
P. S. Если в комментариях будет интерес к теме, напишу вторую статью о том, как DecoratorHelper устроен. Но сразу скажу, что это уровень Junior+.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_python, #_decoratorhelper, #_dekoratory (Декораторы), #_python, #_piton (Питон), #_pajton (Пайтон), #_decorators, #_python
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 09:01
Часовой пояс: UTC + 5