Ordre d'exécution du décorateur

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

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

Je comprends à peu près les décorateurs et comment cela fonctionne avec l'un d'entre eux dans la plupart des exemples.

Dans cet exemple, il y en a 2. De la sortie, il semble que @make_italics'exécute d'abord, puis @make_bold.

Cela signifie-t-il que pour les fonctions décorées, il exécutera d'abord la fonction puis se déplacera vers le haut pour les autres décorateurs? Comme d' @make_italicabord alors @make_bold, au lieu du contraire.

Cela signifie donc qu'il est différent de la norme de l'approche descendante dans la plupart des langages de programmation? Rien que pour cette affaire de décorateur? Ou ai-je tort?

Débutant
la source
4
oui ça commence par le bas en passant le résultat au suivant
Padraic Cunningham
1
Le commentaire de @PadraicCunningham est également une partie importante de la réponse. Eu un problème connexe ( stackoverflow.com/questions/47042196/… )
shookees
Je dirais que c'est toujours du haut vers le bas, dans le sens où a(b(x))c'est du haut vers le bas (si vous imaginez cela divisé sur 3 lignes)
joel

Réponses:

126

Les décorateurs enveloppent la fonction qu'ils décorent. Donc make_bolddécoré le résultat du make_italicdécorateur, qui a décoré la hellofonction.

La @decoratorsyntaxe n'est en réalité que du sucre syntaxique; le suivant:

@decorator
def decorated_function():
    # ...

est vraiment exécuté comme:

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

en remplaçant l' decorated_functionobjet d' origine par ce qui decorator()est retourné.

L'empilement des décorateurs répète ce processus vers l'extérieur .

Donc votre échantillon:

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

peut être étendu à:

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

Lorsque vous appelez hello()maintenant, vous appelez l'objet renvoyé par make_bold(), vraiment. make_bold()a renvoyé un lambdaqui appelle la fonction make_boldencapsulée, qui est la valeur de retour de make_italic(), qui est également un lambda qui appelle l'original hello(). En développant tous ces appels, vous obtenez:

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

donc la sortie devient:

"<b>" + ("<i>" + ("hello world") + "</i>") + "</b>"
Martijn Pieters
la source
Je comprends. Mais cela signifie-t-il que lorsqu'il y a 2 wrappers dans ce cas, l'EDI détecte et encapsule automatiquement le résultat du premier wrapper? Parce que je pensais ça @make_bold #make_bold = make_bold(hello) @make_italic #make_italic = make_italic (hello)? Je ne suis pas sûr que sur cette base, il encapsulera le premier résultat. Ou pour ce cas de 2 wrappers, l'EDI utilisera make_bold(make_italic(hello))comme vous l'avez mentionné au lieu de ce que j'ai partagé?
Débutant
3
@Newbie: Votre IDE ne fait rien ici; c'est Python qui fait le wrapping. Je vous ai montré dans mon dernier échantillon qui make_bold()encapsule la sortie de make_italic(), qui a été utilisé pour envelopper hello, donc l'équivalent de make_bold(make_italic(hello)).
Martijn Pieters
Pourriez-vous fournir une version de ce code sans utiliser lambda? J'avais essayé .format mais ne fonctionne pas. Et pourquoi lambda est-il utilisé dans cet exemple? J'essaie de comprendre lambda et comment cela fonctionne dans cet exemple, mais j'ai toujours des problèmes. Je comprends que lambda est comme des fonctions à une ligne qui peuvent être passées beaucoup plus facilement par rapport à la norme des fonctions def?
Débutant
def inner: return "<b>" + fn() + "</b>", alors return innerserait la version de fonction «régulière»; pas si grande différence.
Martijn Pieters
Je suis toujours confus au sujet de l'ordre. "... les décorateurs seront appliqués en commençant par celui qui se rapproche le plus de l'instruction" def "" J'appelle cela "à l'envers". Je pense que Martijn appelle cela «vers l'extérieur». Cela signifie que le make_italic décorateur est exécuté avant le make_bold décorateur , car il make_italicest le plus proche du def. Cependant, j'oublie l' ordre d'exécution du code décoré : le make_bold décoré (c'est-à-dire le lambda gras) est exécuté en premier, suivi du lambda make_italic décoré (c'est-à-dire le lambda en italique).
The Red Pea