I think the answer to this question seems straightforward but actually it isn't. When we talk about the order of decorators I think we have to keep in mind that the decorators themselves are evaluated in different moments during the executing: 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 make things even more complicated when nesting decorators.
So, in few words:
- When the interpreter is evaluating the decorated method definition the decorators are evaluated from bottom --> top
- When the interpreter calls the decorated method the decorators are called from top --> bottom.
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 to 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 we 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 shed some light on the decorators order in Python and how it's handled.