155
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?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Newbie
  • 1,677
  • 2
  • 12
  • 14
  • 7
    yes it starts from the bottom up passing the result to the next – Padraic Cunningham Dec 07 '14 at 11:28
  • 1
    @PadraicCunningham comment is as well an important portion of the answer. Had a related problem (https://stackoverflow.com/questions/47042196/why-are-my-python-mock-patches-appearing-in-the-wrong-order) – psukys Jul 03 '18 at 11:18
  • I'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) – joel Oct 05 '18 at 08:21

2 Answers2

204

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>"
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I 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 use `make_bold(make_italic(hello))` as you have mentioned instead of what I shared? – Newbie Dec 07 '14 at 11:38
  • 5
    @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 of `make_italic()`, which was used to wrap `hello`, so the equivalent of `make_bold(make_italic(hello))`. – Martijn Pieters Dec 07 '14 at 11:40
  • Could 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? – Newbie Dec 07 '14 at 12:20
  • `def inner: return "" + fn() + ""`, then `return inner` would be the 'regular' function version; not that big a difference. – Martijn Pieters Dec 07 '14 at 20:49
  • 1
    I always get confused about order. ["...decorators will be applied starting with the one closest to the "def" statement"](https://wiki.python.org/moin/PythonDecorators#Order_of_Decorators) I call this "inside-out". I think Martijn calls this "outward". This means `make_italic` **decorator** is executed before `make_bold` **decorator**, because `make_italic` is closest to the `def`. However, I forget that **decorated** code execution order: the `make_bold` **decorated** (i.e. bold lambda) is executed first, followed by the `make_italic` **decorated** lambda (i.e. italic lambda). – Nate Anderson Nov 27 '17 at 23:34
  • @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 ? – Dhiwakar Ravikumar Nov 29 '20 at 23:43
  • @DhiwakarRavikumar: I think you are looking for [my explanation of how `@property` works](https://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work-in-python/17330273#17330273). – Martijn Pieters Nov 29 '20 at 23:55
  • @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 -> https://stackoverflow.com/questions/65065914/decorating-with-property-results-in-additional-argument-being-passed the property() , can you please explain why the difference exists ? I am not able to find the answer in your explanation. – Dhiwakar Ravikumar Nov 30 '20 at 00:22
  • @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 https://docs.python.org/3/howto/descriptor.html – Martijn Pieters Nov 30 '20 at 14:04
24

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:

  • 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 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.

rkachach
  • 16,517
  • 6
  • 42
  • 66
  • I can see that when you run `hello()` the `print("make_bold")` and `print("make_italic")` didn't run again like they were in the Definition process, which means that in the Execution process what is being run is the return values(in this case are the lambda functions) of the decorators. – Danh Le Jun 28 '23 at 09:23