
Всем привет! В этой статье я расскажу об инструменте, разработанном мной, который изменяет работу декораторов в 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+.
ссылка на оригинал статьи https://habr.com/ru/post/560572/
Добавить комментарий