Reputation: 1927
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?
Upvotes: 179
Views: 80625
Reputation: 618
Bare in mind Python's behavior only depends on how its authors wanted it... It's hence always extremely helpful to simply refer to the official documents, where they state:
The current syntax for function decorators as implemented in Python 2.4a2 is:
@dec2
@dec1
def func(arg1, arg2, ...):
pass
This is equivalent to:
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))
Note the last line in the code snippet: dec1 is firstly evaluated, which returns the inner-most function object that is not yet executed, then dec2 is evaluated and returns a function object, etc, and finally we get the hierarchically wrapped function. In this phrase, the execution order is definition part of dec1, dec2, ..., decN.
In the end, there's only one single function hierarchically wrapped gets called, when calling, the execution is in a reversed order.
To help memorize/understand, imagine you are wrapping a gift, you start from the innermost and onwards. When unwrap, you do it reversely.
A follow up:
Just noticed @rkachach did some decent experiments. Among which, the execution order of def decorator(extra) seems to violate the order of definition parts given above. It turns out to be a special case that is also discussed in the same official doc, see below, note how the parens are grouped to form the order:
The current syntax also allows decorator declarations to call a function that returns a decorator:
@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
pass
This is equivalent to:
func = decomaker(argA, argB, ...)(func)
Upvotes: 1
Reputation: 17345
I think the answer to this question seems straightforward but itsn't. When we talk about the decorator's order of execution I think we have to remember that the decorators themselves are evaluated at different moments during the execution: when the Python interpreter is evaluating the decorated method definition itself and when the decorated method is called/executed. The order of decorators as I could see in my experiments is different between these two phases.
Besides, keep in mind that when decorating a function we could have code that executes before the decorated method and code that runs after. This makes things even more complicated when nesting decorators.
So, in a few words:
Consider the following code example:
print("========== Definition ==========")
def decorator(extra):
print(" in decorator factory for %s " % extra)
extra = " %s" % extra
def inner(func):
print(" defining decorator %s " % extra)
def wrapper(*args, **kwargs):
print("before %s -- %s" % (func.__name__, extra))
func(*args, **kwargs)
print("after %s -- %s" % (func.__name__, extra))
return wrapper
return inner
@decorator('first')
@decorator('middle')
@decorator('last')
def hello():
print(' Hello ')
print("\n========== Execution ==========")
hello()
The outtput of this code is the following:
========== Definition ==========
in decorator factory for first
in decorator factory for middle
in decorator factory for last
defining decorator last
defining decorator middle
defining decorator first
========== Execution ==========
before wrapper -- first
before wrapper -- middle
before hello -- last
Hello
after hello -- last
after wrapper -- middle
after wrapper -- first
As we can see in this output the order is different (as explained before). During the definition, decorators are evaluated from bottom to top meanwhile during the execution (which is the most important part in general) they are evaluated from top to bottom.
Going back the example proposed in the question, following is a sample code (without using lambda):
print("========== Definition ==========")
def make_bold(fn):
print("make_bold decorator")
def wrapper():
print("bold")
return "<b>" + fn() + "</b>"
return wrapper
def make_italic(fn):
print("make_italic decorator")
def wrapper():
print("italic")
return "<i>" + fn() + "</i>"
return wrapper
@make_bold
@make_italic
def hello():
return "hello world"
print("\n========== Execution ==========")
print(hello())
The output in this case:
========== Definition ==========
make_italic decorator
make_bold decorator
========== Execution ==========
bold
italic
<b><i>hello world</i></b>
Newly the order of execution is from top to bottom. We can apply the same to the original code (a little bit modified to print where are we):
print("========== Definition ==========")
def make_bold(fn):
print("make_bold")
return lambda: print("exec_bold") or "<b>" + fn() + "</b>"
def make_italic(fn):
print("make_italic")
return lambda: print("exec_italic") or "<i>" + fn() + "</i>"
@make_bold
@make_italic
def hello():
return "hello world"
print("\n========== Execution ==========")
print(hello())
The output is:
========== Definition ==========
make_italic
make_bold
========== Execution ==========
exec_bold
exec_italic
<b><i>hello world</i></b>
I hope this sheds some light on the decorators order in Python and how it's handled.
Upvotes: 33
Reputation: 1121814
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>"
Upvotes: 221