Всем привет! В этой статье я расскажу об инструменте, разработанном мной, который изменяет работу декораторов в Python и делает их более «Питоничными».
Я не буду рассказывать про области применения декораторов. Есть множество статей на эту тему.
Для начала, давайте вспомним: что же такое декораторы в Пайтон.
Если совсем просто, то это удобный способ передать одну функцию в другую и получить третью. В этом определении нет ни одного слова правды, но мы вернёмся к этому позже.
Давайте разбираться!
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!
Выходим из обёртки
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!
Как Вам код? Вспомним, мантру Питонистов из начала статьи:
pip install DecoratorHelper
Импортируем и используем как декоратор:
from DecoratorHelper import DecoratorHelper
@DecoratorHelper
def hello_world(text):
print(text)
hello_world('Hello, world!')
Что это даёт?
Перепишем приведённый ранее код декоратора, который может принимать/не принимать аргументы:
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 объекты, иначе всё сломается Думаю, для большинства задач, это не смертельно...
Что дальше?
В следующих версиях планируется:
Источник статьи: https://habr.com/ru/post/560572/
Я не буду рассказывать про области применения декораторов. Есть множество статей на эту тему.
Для начала, давайте вспомним: что же такое декораторы в Пайтон.
Если совсем просто, то это удобный способ передать одну функцию в другую и получить третью. В этом определении нет ни одного слова правды, но мы вернёмся к этому позже.
Давайте разбираться!
Как работают декораторы
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.
Источник статьи: https://habr.com/ru/post/560572/