In this introductory tutorial, we’ll look at what decorators are and how to create and use them. Decorators provide a simple syntax for calling higher-order functions. By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it. Sounds confusing – but it’s really not, especially after we go over a number of examples.
Functions
Before you can understand decorators, you must first understand how functions work. Essentially, functions return a value based on the given arguments.
|
First Class Objects
In Python, functions are first-class objects. This means that functions can be passed around, and used as arguments, just like any other value (e.g, string, int, float).
|
Nested Functions
Because of the first-class nature of functions in Python, you can define functions inside other functions. Such functions are called nested functions.
|
What happens when you call the parent()
function? Think about this for a minute. You should get…
|
Try calling the first_child()
. You should get an error:
|
What have we learned?
Whenever you call parent()
, the sibling functions, first_child()
and second_child()
are also called AND because of scope, both of the sibling functions are not available (e.g., cannot be called) outside of the parent function.
Returning Functions
Python also allows you to return functions from other functions. Let’s alter the previous function for this example.
|
The output of the first two print statements is:
|
This simply means that foo
points to the first_child()
function, while bar
points to the second_child()
function.
The output of the second two functions confirms this:
|
Finally, did you notice that in example three, we executed the sibling functions within the parent functions – e.g, second_child()
. Meanwhile in this last example, we did not add parenthesis to the sibling functions – first_child
– when called so that way we can use them in the future. Make sense?
Now, my friend, you are ready to take on decorators!
Decorators
Let’s look at two examples …
Example 1:
|
Can you guess what the output will be? Try.
|
To understand what’s going on here, just look back at the four previous examples. We are literally just applying everything learned. Put simply, decorators wrap a function, modifying its behavior.
Let’s take it one step further and add an if statement.
Example 2:
|
This will output in:
|
Syntactic sugar!
Python allows you to simplify the calling of decorators using the @
symbol (this is called “pie” syntax).
Let’s create a module for our decorator:
|
Okay. Stay with me. Let’s look at how to call the function with the decorator:
|
When you run this example, you should get the same output as in the previous one:
|
So, @my_decorator
is just an easier way of saying just_some_function = my_decorator(just_some_function)
. It’s how you apply a decorator to a function.
Real World Examples
How about a few real world examples …
|
This returns the time before you run my_function()
as well as the time after. Then we simply subtract the two to see how long it took to run the function.
Run it. Work through the code, line by line. Make sure you understand how it works.
|
This decorator is used for rate limiting. Test it out.
One of the most used decorators in Python is the login_required()
decorator, which ensures that a user is logged in/properly authenticated before s/he can access a specific route (/secret
, in this case):
|
Did you notice that the function gets passed to the functools.wraps()
decorator? This simply preserves the metadata of the wrapped function.
Let’s look at one last use case. Take a quick look at the following Flask route handler:
|
Here we ensure that the key student_id
is part of the request. Although this validation works it really does not belong in the function itself. Plus, perhaps there are other routes that use the exact same validation. So, let’s keep it DRY and abstract out any unnecessary logic with a decorator.
|
In the above code, the decorator takes a variable length list as an argument so that we can pass in as many string arguments as necessary, each representing a key used to validate the JSON data. Did you notice that this dynamically creates new decorators based on those strings? Test this out.
Cheers!