Understanding Python decorators: six levels of decorators from beginner to expert

How decorators work, when to use them and 6 examples in increasingly complexity

Understanding Python decorators: six levels of decorators from beginner to expert
This code is pretty decorated already (image by Intricate Explorer on Unsplash)

Understanding Python decorators: Six levels of decorators from beginner to expert

Decorators are a very handy tool that can be used to change the behavior of a function without modifying the function itself. They allow you to easily add functionality without changing existing code. There are many use-cases (some of which we’ll get into today) like logging, performance checking, verifying permissions etc.

After this article you have a clear understanding how decorators work, how to apply them and when to apply them. We’ll start with the easiest, most basic example and then slowly work up our way to more complex ones. In the end we’ll have a function, decorated with multiple decorators of different types. Let’s code!


How decorators work

First we’ll specify a function that we want to decorate:

As you can see this function is called ‘sayhello’ and we can execute it by running sayhello() (notice the () after the function name). This is different from print(sayhello); this returns the function name and the memory address where the function is stored:
<function sayhello at 0x000001F64CA25A60>

The next step is to create a decorator:

As you see we create a function that expects another function. Then we define an inner function called wrapper in which we execute the received function. Then we return the inner function. The reason for defining the inner function is to handle arguments, we’ll see this in the next chapters.

We can use this decorator like this our_decorator(sayhello()) or add a little syntactic sugar and pass it like this:

Both methods are equivalent: we wrap sayhello in our_decorator.


Decorators in 6 steps of complexity

Now that we understand the way decorators and their syntax works we’ll go through 6 steps of decorators that show you all the possibilities and flexibility they offer.

Let’s start decorating (imag by Daniel Cheung on Unsplash)

1. The simplest decorator

To begin with we create one of the most basic examples of a decorator. We create a function called logging_decorator that will be used to decorate other functions. First we check out the code and then go through:

We are re-using the old sayhello fucntion as you can see. We decorate it with a function called logging_decorator. As you see in lines 3–5 we can do something before and after we actually execute sayhello so let’s print out the fact that we call this function for debugging purposes.

Also notice that we have access to the function as an object. This way we can call the function’s name in line 3 and 5. Executing the code below prints out the following:[LOG]   calling sayhello
=== Hello from the function function
[LOG] called sayhello

Notice that our function is pretty simple; it doesn’t even return anything. In the next part we’ll up the complexity a bit more.


2. Passing arguments and a return value

In the previous part our decorator is merely executing the wrapped function. We’ll upgrade the code so that the function receives arguments and actually returns something. This is where the inner function called wrapper comes in.

In the code above we decorate the multiply function. This function needs two arguments. We modify the wrapper inside the decorator function with *args and **kwargs on line 2. Then we then pass them to the decorated function on line 4. On this same line we also receive the result of the decorated function that we then return on line 6. These minor changes make it so that we can receive a result from our target function:[LOG]   calling multiply
 === Inside the multiply function
[LOG] called multiply
[result]   20


3. Multiple decorators

It is also possible to decorate a function with multiple decorators.

The code above first wraps multiply in logging_decorator_2 and then wraps the whole bunch in logging_decorator_1. It works from the target-function and moves upwards through all decorators. You can see in the output below in what order the decorators are called:[LOG1]   calling decorator 1
[LOG2]   calling decorator 2
multiply function
[LOG2] called decorator 2
[LOG1] called decorator 1
[result]   42

Calling the target function with our two decorators closely resembles calling it like the line of code below. Both are equivalent but in the line below the wrapper-like attributes are really clear:
logging_decorator_1(logging_decorator_2(multiply(14,3)))


4. Passing arguments to decorators

In this part we’ll modify the logging decorator a bit so that it can receive arguments. We want to receive one argument that indicates whether debug-mode is on or off. If it’s on we print out the logging lines, otherwise we don’t.

The changes are in line 1; here we allow the decorator to accept a variable. Then in the wrapper we can access this variable and determine whether to print out the line or not. We can determine whether the debug-mode is on or off in line 13.


5 Decorator classes that have state

In this part we’ll create a decorator that counts the number of times the target function is called. In order to do this the decorator needs to conserve some state: it has to remember the number of times a function was called:

As you see we initialize the class with the target-function. Then, when the target-function is called we increment the call_count. This is how to use this decorator:

Executing this will produce the following result:Called multiply for the 1th time
Called multiply for the 2th time
Called multiply for the 3th time
Called multiply for the 4th time
res: (42, 70, 54, 8)

Keeping track of how often a function is called can be really convenient for limiting API calls e.g.


6. Interaction between decorator args and function args

In this last step we’ll create a decorator that can secure a function with a password. In the decorator argument we’ll define the password. We will check this with the password that’s provided through the target-function. Similar decorators are commonly used in providing security for API routes e.g.

Keep in mind that the example below just serves as an example; this is not a great example for secure code. Check out this article for more information on how to securely store confidential information in your code.

As you see we provide our valid password in the decorator on line 14. Then, inside the decorator, we take the password that’s provided through the target function from the args or kwargs on line 5. Then, on line 6, we check if the provided password is correct. Executing the code above results in the output below:You can only see this if you provided the right answer
Incorrect password

Decorators allow you to create true masterpieces (image by Igor Miske on Unsplash)

Conclusion

In this article I’ve tried to explain why and when you want to use decorators. Then we’ve gone through 6 increasingly complex examples with which I hope to have shown the flexibility that decorators can bring.

If you have suggestions/clarifications please comment so I can improve this article. In the meantime, check out my other articles on all kinds of programming-related topics like these:

Happy coding!

— Mike

P.S: like what I’m doing? Follow me!

Join Medium with my referral link — Mike Huls
As a Medium member, a portion of your membership fee goes to writers you read, and you get full access to every story…