Orden de ejecución del 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()

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

Entiendo aproximadamente sobre decoradores y cómo funciona con uno de ellos en la mayoría de los ejemplos.

En este ejemplo, hay 2. De la salida, parece que se @make_italicejecuta primero, luego @make_bold.

¿Significa esto que para las funciones decoradas, primero ejecutará la función y luego se moverá hacia la parte superior para otros decoradores? Como @make_italicprimero entonces @make_bold, en lugar de lo contrario.

Entonces, ¿esto significa que es diferente de la norma del enfoque de arriba hacia abajo en la mayoría de los lenguajes de programación? ¿Solo para este caso de decorador? ¿O me equivoco?

Novato
fuente
4
sí, comienza de abajo hacia arriba pasando el resultado al siguiente
Padraic Cunningham
1
El comentario de @PadraicCunningham es también una parte importante de la respuesta. Tuve un problema relacionado ( stackoverflow.com/questions/47042196/… )
shookees
Yo diría que sigue siendo de arriba hacia abajo, en el sentido de que a(b(x))es de arriba hacia abajo (si imaginas que se divide en 3 líneas)
joel

Respuestas:

126

Los decoradores envuelven la función que están decorando. Así make_bolddecoró el resultado del make_italicdecorador, que decoró la hellofunción.

La @decoratorsintaxis es realmente azúcar sintáctica; el seguimiento:

@decorator
def decorated_function():
    # ...

se ejecuta realmente como:

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

reemplazando el decorated_functionobjeto original con lo que sea decorator()devuelto.

Los decoradores de apilamiento repiten ese proceso hacia afuera .

Entonces tu muestra:

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

se puede ampliar a:

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

Cuando llamas hello()ahora, realmente estás llamando al objeto devuelto por make_bold(). make_bold()devolvió una lambdaque llama a la función make_boldenvuelta, que es el valor de retorno de make_italic(), que también es una lambda que llama al original hello(). Al expandir todas estas llamadas, obtienes:

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

por lo que la salida se convierte en:

"<b>" + ("<i>" + ("hello world") + "</i>") + "</b>"
Martijn Pieters
fuente
Entiendo. Pero, ¿significa esto que cuando hay 2 envoltorios en este caso, el IDE detectará y ajustará automáticamente el resultado del primer envoltorio? ¿Porque pensé eso @make_bold #make_bold = make_bold(hello) @make_italic #make_italic = make_italic (hello)? No estoy seguro de si basándome en esto, envolverá el primer resultado. O para este caso de 2 envoltorios, ¿el IDE usará make_bold(make_italic(hello))como mencionaste en lugar de lo que compartí?
Novato
3
@Newbie: Su IDE no hace nada aquí; es Python el que lo envuelve. Les mostré en mi última muestra que make_bold()envuelve la salida de make_italic(), que se usó para envolver hello, por lo que el equivalente de make_bold(make_italic(hello)).
Martijn Pieters
¿Podría proporcionar una versión de este código sin el uso de lambda? Intenté .format pero no funciona. ¿Y por qué se usa lambda en este ejemplo? Estoy tratando de entender lambda y cómo funciona en este ejemplo, pero sigo teniendo problemas. Entiendo que lambda es como funciones de una línea que se pueden pasar con mucha facilidad en comparación con la norma de funciones def.
Novato
def inner: return "<b>" + fn() + "</b>", entonces return innersería la versión de función 'regular'; no es una gran diferencia.
Martijn Pieters
Siempre me confunde el orden. "... los decoradores se aplicarán comenzando con el más cercano a la declaración" def "" Yo llamo a esto "de adentro hacia afuera". Creo que Martijn llama a esto "hacia afuera". Esto significa que el make_italic decorador se ejecuta antes que el make_bold decorador , porque make_italices el más cercano al def. Sin embargo, olvido que el orden de ejecución del código decorado : el make_bold decorado (es decir, lambda en negrita) se ejecuta primero, seguido por el lambda make_italic decorado (es decir, lambda en cursiva).
The Red Pea