Decorator execution order
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?
Solution 1:
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>"