Ordem de execução do decorador

93
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()

Resultado: "<b><i>hello world</i></b>"

Eu entendo aproximadamente sobre decoradores e como funciona com um deles na maioria dos exemplos.

Neste exemplo, existem 2 deles. Pela saída, parece que é @make_italicexecutado primeiro, então @make_bold.

Isso significa que, para funções decoradas, ele primeiro executará a função primeiro e depois avançará para o topo para outros decoradores? Como @make_italicprimeiro então @make_bold, em vez do contrário.

Então, isso significa que é diferente da norma de abordagem de cima para baixo na maioria das linguagens de programação? Só para esse caso de decorador? Ou eu estou errado?

Novato
fonte
4
sim, começa de baixo para cima passando o resultado para o próximo
Padraic Cunningham
1
O comentário de @PadraicCunningham também é uma parte importante da resposta. Tive um problema relacionado ( stackoverflow.com/questions/47042196/… )
shookees
Eu diria que ainda é de cima para baixo, no sentido de que a(b(x))é de cima para baixo (se você imaginar que está dividido em 3 linhas)
joel

Respostas:

126

Os decoradores envolvem a função que estão decorando. Assim make_bolddecorou o resultado do make_italicdecorador, que decorou a hellofunção.

A @decoratorsintaxe é apenas um açúcar sintático; Os seguintes:

@decorator
def decorated_function():
    # ...

é realmente executado como:

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

substituindo o decorated_functionobjeto original com o que for decorator()retornado.

Empilhar decoradores repete esse processo externamente .

Então, sua amostra:

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

pode ser expandido para:

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

Quando você chama hello()agora, está chamando o objeto retornado por make_bold(), na verdade. make_bold()retornou um lambdaque chama a função make_boldempacotada, que é o valor de retorno de make_italic(), que também é um lambda que chama o original hello(). Expandindo todas essas ligações que você recebe:

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

então a saída se torna:

"<b>" + ("<i>" + ("hello world") + "</i>") + "</b>"
Martijn Pieters
fonte
Compreendo. Mas isso significa que quando houver 2 wrappers nesse caso, o IDE detectará e agrupará automaticamente o resultado do primeiro wrapper? Porque eu pensei isso @make_bold #make_bold = make_bold(hello) @make_italic #make_italic = make_italic (hello)? Não tenho certeza se com base nisso, envolverá o primeiro resultado. Ou, para este caso de 2 wrappers, o IDE usará make_bold(make_italic(hello))como você mencionou em vez do que eu compartilhei?
Novato
3
@Newbie: Seu IDE não faz nada aqui; é o Python que faz o embrulho. Eu mostrei a você em meu último exemplo que make_bold()envolve a saída de make_italic(), que foi usado para envolver hello, portanto, o equivalente de make_bold(make_italic(hello)).
Martijn Pieters
Você poderia fornecer uma versão deste código sem o uso de lambda? Eu tentei .format, mas não funciona. E por que lambda é usado neste exemplo? Estou tentando entender lambda e como funciona neste exemplo, mas ainda estou tendo problemas. Eu entendi que lambda é como funções de uma linha que podem ser passadas muito facilmente em comparação com a norma das funções def.
Novato
def inner: return "<b>" + fn() + "</b>", então return innerseria a versão da função 'regular'; não é uma diferença tão grande.
Martijn Pieters
Sempre fico confuso com a ordem. "... decoradores serão aplicados começando com aquele mais próximo da declaração" def "" Eu chamo isso de "avesso". Acho que Martijn chama isso de "exterior". Isso significa que o make_italic decorator é executado antes do make_bold decorator , porque make_italicestá mais próximo do def. No entanto, esqueço a ordem de execução do código decorado : o make_bold decorado (isto é, lambda em negrito) é executado primeiro, seguido pelo lambda make_italic decorado (isto é, lambda em itálico).
The Red Pea