Порядок выполнения декоратора

96
def make_bold(fn):
    return lambda : "<b>" + fn() + "</b>"

def make_italic(fn):
    return lambda : "<i>" + fn() + "</i>"

@make_bold
@make_italic
def hello():
  return "hello world"

helloHTML = hello()

Выход: "<b><i>hello world</i></b>"

Я примерно понимаю декораторы и то, как они работают с одним из них, в большинстве примеров.

В этом примере их 2. Из вывода кажется, что @make_italicсначала выполняется, затем @make_bold.

Означает ли это, что для декорированных функций он сначала запускает функцию, а затем перемещается вверх для других декораторов? Вроде @make_italicсначала тогда @make_bold, а не наоборот.

Значит, это означает, что это отличается от нормального подхода сверху вниз в большинстве языков программирования? Как раз на этот случай декоратора? Или я не прав?

Новичок
источник
4
да, он начинается снизу вверх, передавая результат следующему
Падраик Каннингем
1
Комментарий @PadraicCunningham также является важной частью ответа. Была связанная проблема ( stackoverflow.com/questions/47042196/… )
потрясает
Я бы сказал, что это все еще нисходящий, в том смысле, что a(b(x))это нисходящий (если вы представите, что это разделение на 3 строки)
Джоэл

Ответы:

131

Декораторы оборачивают функцию, которую они украшают. Так make_boldоформлен результат make_italicдекоратора, украсившего helloфункцию.

@decoratorСинтаксис действительно просто синтаксический сахар; продолжение:

@decorator
def decorated_function():
    # ...

действительно выполняется как:

def decorated_function():
    # ...
decorated_function = decorator(decorated_function)

замена исходного decorated_functionобъекта тем, что было decorator()возвращено.

Укладка декораторы повторы , которые обрабатывают наружу .

Итак, ваш образец:

@make_bold
@make_italic
def hello():
  return "hello world"

можно расширить до:

def hello():
  return "hello world"
hello = make_bold(make_italic(hello))

Когда вы вызываете hello()сейчас, вы действительно вызываете объект, возвращаемый make_bold(). make_bold()вернул a, lambdaкоторый вызывает make_boldобернутую функцию , которая является возвращаемым значением make_italic(), которое также является лямбда, вызывающим оригинал hello(). Расширяя все эти звонки, вы получаете:

hello() = lambda : "<b>" + fn() + "</b>" #  where fn() ->
    lambda : "<i>" + fn() + "</i>" # where fn() -> 
        return "hello world"

поэтому вывод становится:

"<b>" + ("<i>" + ("hello world") + "</i>") + "</b>"
Мартейн Питерс
источник
Я понимаю. Но означает ли это, что, когда в этом случае есть 2 оболочки, IDE автоматически обнаружит и обернет результат первой оболочки? Потому что я так думал @make_bold #make_bold = make_bold(hello) @make_italic #make_italic = make_italic (hello)? Я не уверен, что, основываясь на этом, он завершит первый результат. Или в этом случае с двумя оболочками среда IDE будет использовать, make_bold(make_italic(hello))как вы упомянули, вместо того, что я поделился?
Новичок
3
@Newbie: Ваша IDE здесь ничего не делает; это Python, который делает упаковку. Я показал вам в моем последнем примере, который make_bold()обертывает вывод make_italic(), который использовался для обертывания hello, поэтому эквивалент make_bold(make_italic(hello)).
Мартин Питерс
Не могли бы вы предоставить версию этого кода без использования лямбда? Я пробовал .format, но не работает. А почему в этом примере используется лямбда? Я пытаюсь понять лямбда и то, как она работает в этом примере, но все еще не понимаю. Я понимаю, что лямбда похожа на однострочные функции, которые можно передать намного проще по сравнению с нормой функций def?
Новичок
def inner: return "<b>" + fn() + "</b>", тогда return innerбудет «обычная» версия функции; не такая уж большая разница.
Мартейн Питерс
Меня всегда путает порядок. «... декораторы будут применяться, начиная с ближайшего к оператору def». Я называю это «наизнанку». Я думаю, что Мартейн называет это «внешним». Это означает, что make_italic декоратор выполняется перед make_bold декоратором , поскольку make_italicон ближе всего к def. Однако я забываю, что порядок выполнения декорированного кода: make_bold декорированная (то есть жирная лямбда) выполняется первой, а затем make_italic декорированная лямбда (то есть курсивная лямбда).
The Red Pea