Как работает декоратор внутри?
У нас есть простой декоратор и функция, которой мы хотим добавить дополнительный функционал:
def wrap(func):
def mod():
up = func()
mod_up = up.upper()
return mod_up
return mod
def up_str():
return 'hello Maksim'
Задекорировать и вызвать нашу исходную функцию мы можем тремя способами :
#1) Просто вызывать функцию wrap c передачей ей в качестве параметра функции uper_str.
print(wrap(up_str)()) #Вывод: HELLO MAKSIM Важным моментом является то, что мы всегда возвращаем в return не результат вызова
#функции(с круглыми скобочками), а сам объект функции(без круглых скобочек).
# Этап вызова нашей функции ^^^ когдa выполняется return mod, объект функции mod перемещается в область видимости wrap, что образно можно представить так:
def wrap(func):
mod
# А затем к возвращённому mod применяется оператор круглых скобочек (), которые мы дописали в конце вызова wrap выше. То есть вызывается функция mod() - происходит то же самое, что и в предыдущий раз тело функции mod как бы перемещается в область видимости wrap:
def wrap(func):
up = func()
mod_up = up.upper() # эти две строчки сразу возвращаются в виде результата вызова mod() - return mod_up. Это просто образное представление происходящих процессов, чтобы в том числе объяснить, как параметр func может передаться в up = func() - благодаря попадaнию в область видимости wrap.
# (Если что я просто образно представляю порядок выполнения кода)
#2) Переопределить функцию up_str. Логика "превращений" та же только добавляется новое звено: wrap(up_str)-->mod-->up_str. Чтобы активировать переопределённую функцию к ней так же надо добавить оператор круглых скобок () :
up_str = wrap(up_str)
print(up_str()) #Вывод: HELLO MAKSIM
# В этом случае после переопределения функции up_str мы теряем её исходный функционал, но мы всегда можем сохранить исходную функцию в другую переменную до её переопределения и таким образом ничего не потерять.
#3) Ну и с помощью синтаксического сахара:
@wrap
def up_str():
return 'hello Maksim'
print(up_str())#Вывод: HELLO MAKSIM
В первых двух способах функция декорируется на этапе вызова функции. В последнем, на этапе определения функции. Отсюда следуют вопросы:
- Что последовательно происходит при декорировании через @, на этапе определения up_str?
- Как сохранить и исходную функцию, и задекорированную при использовании @?
Ответы (1 шт):
1 При подключении модуля код модуля выполняется. Питон выполняет код модуля "сверху вниз", тем самым заполняя пространство имен. Когда при этом выполнении он встречает декоратор @decoratorname
(который по существу Callable), то он вызывает его как callable (на этапе определения декодируемого объекта, когда он уже распарсил декорируемый объект, но еще не впихнул его в пространство имен), передавая ему декорируемый объект (ведь всё есть объект в питоне и определяемая функция тоже).
Далее эта функция-декоратор должна что то вернуть - и это попадет в пространство имен под именем декорируемого объекта. Эквивалентно вашему up_str = wrap(up_str)
.
Но можно хоть 42 вернуть.
В данном примере вместо функции up_str
под именем up_str
в пространстве имен будет число 42.
def wrap(func):
return 42
@wrap
def up_str():
return 'hello Maksim'
print(type(up_str))
# <class 'int'>
2 не будет никакой исходной функции в итоге. в месте вызова декоратора будет всегда результат выполнения декоратора. Что бы это ни было. Вот в нем вы можете сохранять что угодно и как угодно. Обычно возвращают обертку на функцией, а внутри обертки хранится оригинальная функция.
ps: существует functools.wraps
который делает всю грязную работу при создании обертке, а это не только само оборачивание.