Python Friday #37: Decorators

Decorators are an interesting concept in Python. We use them in Flask to configure routing, but they are not limited to this use case. Let us find out what we can do with them and how they work.

This post is part of my journey to learn Python. You can find the other parts of this series here.

 

Everything in Python is an object

To understand decorators, we need to take a closer look at functions in Python. Everything in Python is an object, including functions. Therefore, we can do with functions everything we can do with an object – including passing functions as parameters to other functions, referencing functions with other names and returning a function from a function.

This sounds complicated. Let us switch from English to code to see that it is nothing you not already know when you work with other objects.

 

A closer look at functions

When we create a function, we give it a name. To run that function, we call its name with (). But that name can change, or we can create other names for the same function:

The creation of a function works as we did it before. The interesting part happens on line 4. Here we assign the hello function to the name abc – the same way as we assign any other object to a variable. We are used to write the () when we work with functions. If we would use () here, it would assign the return value to abc. Without the () we assign the function itself.
We now have two names for the same function, and it does not matter if we call our function with the name hello() or abc().

We can pass a function as an argument to another function. For that I create a welcome() function that I can pass to the other() function. The function definition for other() looks like any other function that accepts an argument. The magic happens on line 6, where I add () to the name of that parameter and call the passed-in function:

The trick to understand what is going on is to focus on the (). If they are present, we run the function, if not we pass the function around.

Returning functions from a function looks unfamiliar. We create a function definition inside a function definition and return that inner function. If we assign the function to the name x, we can call that inner function as we would run any ordinary function:

When we call x without the () we see the object identifier that tells us hi is part of the local scope of the greeting function:

The scope of variables and functions can fill a blog post on its own. So far, I had no problems that needed a closer look into that part of Python.

 

Creating a decorator

A decorator is basically nothing else than a function that we wrap around another function.

With the little repetition on functions we now have all the parts together to create a decorator:

  • we need to create functions inside functions
  • we return those inner functions
  • we give those functions a new name

First, I create a function my_decorator(), that accepts a function as a parameter. Inside my_decorator() is an inner function header() that has code that runs before the function you put into my_decorator and code that runs after:

The say_hello() function prints a string, what makes it simpler to see an output when we combine them later:

We now wrap the my_decorator around the say_hello function and give this combination the name x. When we run x, it executes the returned function header() that has a part that runs before the say_hello function (the ****) and one that comes after (the -------), what wraps our say_hello function nicely in the middle:

The last trick comes when we assign the decorated function not to a new name (like x), but to the initial function:

This final brain teaser is the consequence (or benefit) of the fact that we can give objects another name. We do that all the time when we assign a new value to an existing variable. Yet when we see that with a function it looks wrong. We nearly never see that in code. The reason for this is that there is a much simpler syntax that we already saw in Flask.

 

The @ syntax

In PEP 318 you find the history and reasoning that lead to a simplification when you use a decorator in Python. Instead of assigning the decorator back to the function, we can use an @ followed by the name of the decorator in our function definition:

Python now does all the assignment for us and we just write an @. That is what happens when we use the app.route decorator in our Flask application:

Instead of writing multiple lines of setup code we use a decorator to get the same result. The @ syntax is simpler to memorize and a lot less error prone.

Decorators start to shine when you do more work inside of them as I did in my examples above. My goal was to focus on the inner working of a decorator and for that complex code is a distraction.

 

Next

Now that we know how the magic behind the route decorator works, it is time to go back to our web application and add a form to it.

2 thoughts on “Python Friday #37: Decorators”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.