Search code examples
pythondecoratorpython-decorators

Flow of python decorator functions


I'm trying to understand the following example I found explaining decorators:

#!/usr/bin/python

def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

def p_decorate(func):
    def func_wrapper(name):
        return "<p>{0}</p>".format(func(name))
    #return "1"
     return func_wrapper


get_text = p_decorate(get_text)

print get_text("John")

The output of this is:

<p>lorem ipsum, John dolor sit amet</p>

I decided to try and change this function up and commented out return func_wrapper and replaced it with return "1".

When I do this, I get the error:

TypeError: 'str' object is not callable

I have 2 questions regarding this:

  1. When the line

    print get_text("John") 
    

    is executed, is

    def func_wrapper(name):
    

    initialised with "John"? What is the sequence of events after this line is run?

  2. Why am I getting this error, because in the end, isn't a string ultimately being returned anyway?

If anyone could explain the flow of events with this code, I would greatly appreciate it.


Solution

  • You called the decorator here:

    get_text = p_decorate(get_text)
    

    Normally, a decorator replaces a function with another callable (another function, for example), or returns the original function but having registered information about it.

    But by changing the return value of p_decorate() to "1" rather than the function wrapper, you 'broke' get_text as that is no longer a function now. You cannot call a string object like you could call a string.

    Before, p_decorate() returned the func_wrapper() function object, so get_text was rebound to point to that function. Calling get_text('John') really called that nested function. func_wrapper() is then indeed called with 'John' as the argument, yes. Functions are after all just objects, you can assign those objects to any valid Python name you like.

    When func_wrapper() is called, it in turn calls func(), which was the argument to p_decorate(). Again, functions are just objects, so having called p_decorate(get_text), func ends up still bound to the original get_text function object. Calling func() calls that original function.

    You can see the full flow of the calls in Python Tutor.