Decorator execution order
Decorators wrap the function they are decorating. So make_bold
decorated the result of the make_italic
decorator, which decorated the hello
function.
The @decorator
syntax is really just syntactic sugar; the following:
@decorator
def decorated_function():
# ...
is really executed as:
def decorated_function():
# ...
decorated_function = decorator(decorated_function)
replacing the original decorated_function
object with whatever decorator()
returned.
Stacking decorators repeats that process outward.
So your sample:
@make_bold
@make_italic
def hello():
return "hello world"
can be expanded to:
def hello():
return "hello world"
hello = make_bold(make_italic(hello))
When you call hello()
now, you are calling the object returned by make_bold()
, really. make_bold()
returned a lambda
that calls the function make_bold
wrapped, which is the return value of make_italic()
, which is also a lambda that calls the original hello()
. Expanding all these calls you get:
hello() = lambda : "<b>" + fn() + "</b>" # where fn() ->
lambda : "<i>" + fn() + "</i>" # where fn() ->
return "hello world"
so the output becomes:
"<b>" + ("<i>" + ("hello world") + "</i>") + "</b>"
Newbie
Updated on April 08, 2022Comments
-
Newbie about 2 years
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()
Output:
"<b><i>hello world</i></b>"
I roughly understand about decorators and how it works with one of it in most examples.
In this example, there are 2 of it. From the output, it seems that
@make_italic
executes first, then@make_bold
.Does this mean that for decorated functions, it will first run the function first then moving towards to the top for other decorators? Like
@make_italic
first then@make_bold
, instead of the opposite.So this means that it is different from the norm of top-down approach in most programming lang? Just for this case of decorator? Or am I wrong?
-
Padraic Cunningham over 9 yearsyes it starts from the bottom up passing the result to the next
-
psukys almost 6 years@PadraicCunningham comment is as well an important portion of the answer. Had a related problem (stackoverflow.com/questions/47042196/…)
-
joel over 5 yearsI'd say it's still top-down, in the sense that
a(b(x))
is top-down (if you imagine that split over 3 lines)
-
-
Newbie over 9 yearsI understand. But does this mean that when there are 2 wrappers in this case, the IDE will automatically detect and wrap the result of the first wrapper? Because I thought that
@make_bold #make_bold = make_bold(hello)
@make_italic #make_italic = make_italic (hello)
? I'm not sure if based on this, it will wrap the first result. Or for this case of 2 wrappers, the IDE will usemake_bold(make_italic(hello))
as you have mentioned instead of what I shared? -
Martijn Pieters over 9 years@Newbie: Your IDE does nothing here; it is Python that does the wrapping. I showed you in my last sample that
make_bold()
wraps the output ofmake_italic()
, which was used to wraphello
, so the equivalent ofmake_bold(make_italic(hello))
. -
Newbie over 9 yearsCould you provide a version of this code without the use of lambda? I had tried .format but doesn't work. And why lambda is used in this example? I'm trying to understand lambda and how it works in this example but still having problems. I get that lambda is like one line functions that can be passed much easily as compared to the norm of def functions?
-
Martijn Pieters over 9 years
def inner: return "<b>" + fn() + "</b>"
, thenreturn inner
would be the 'regular' function version; not that big a difference. -
The Red Pea over 6 yearsI always get confused about order. "...decorators will be applied starting with the one closest to the "def" statement" I call this "inside-out". I think Martijn calls this "outward". This means
make_italic
decorator is executed beforemake_bold
decorator, becausemake_italic
is closest to thedef
. However, I forget that decorated code execution order: themake_bold
decorated (i.e. bold lambda) is executed first, followed by themake_italic
decorated lambda (i.e. italic lambda). -
Dhiwakar Ravikumar over 3 years@MartijnPieters, can you please help me understand why the instance is passed as an argument via args when a regular method is decorated as a property but not when it is decorated by some other method ?
-
Martijn Pieters over 3 years@DhiwakarRavikumar: I think you are looking for my explanation of how
@property
works. -
Dhiwakar Ravikumar over 3 years@MartijnPieters, you have explained this several times before to others but can you please confirm if my understanding is correct, args is non empty and contains an instance of the original calling object and this args belongs to the returned property() object ? In -> stackoverflow.com/questions/65065914/… the property() , can you please explain why the difference exists ? I am not able to find the answer in your explanation.
-
Martijn Pieters over 3 years@DhiwakarRavikumar: I'm not certain what difference you are referring to. I think what is confusing you is how descriptors work. The
property()
object is a descriptor, and so are functions. See docs.python.org/3/howto/descriptor.html