Python Decorators - Deep Dive in 4 Parts (1/4)

Python Decorators - Deep Dive in 4 Parts (1/4)

Intro + build your first decorator

This is a 4 parts series: Part 1 (current): Intro + build your first decorator Part 2: Decorators for functions that receive parameters and return values, decoratorsthat receive arguments, decorators in classes + advanced exercises with solutions Part 3: Concatenating decorators (TBD) Part 4: Property decorator (TBD)

Decorator is a very special python creature that allows to change behavior of existing functions without changing their code. Yes-yes, you read it correctly - changing functions without changing their code. WOW! Let's start our deep dive into this genius python feature.

In this part you will get familiar with decorators, you will learn what decorator is and how it works, and eventually you will implement your first decorator. On the way you will probably discover a thing or two about functions, consider it a bonus ☺

Watch my video about Python Decorators with code examples for better understanding


Motivating task

Let's start from a motivating task. I've implemented a small python package various_functions.py with various useful functions: there are functions for factorial calculation, name capitalizing and temperature conversions. Each function receives an input from a user, performs some calculations, and prints out the result:

various_functions.py module

If we'll run these functions we'll get output as follows:

main.py - calling functions with output

Though this module works fine, I decided to update all of its functions in the following way: when a function starts running, it should first print a beautiful welcome message, then it should perform whatever it performs now, and in the end it should print some nice "good-bye" message before returning.

The naïve way to perform this task would be changing the actual code of all the functions in the module, adding a great chunk of quite annoying work for the developer, as well as introducing a lot of code duplication in the module. And if this is not enough, next time I will add a new function to my module, I will have to remember adding all the messages as needed again.

Quite a mess.

But the good news are that python decorator solves this issue in an efficient and elegant way, just by adding one single line before each function we want to change. So defining my functions as follows will add the exact piece of messaging functionality we need (note the greeting_decorator)

various_functions.py with decorators

Now, when running the same main.py as before, the output will look as follows:

main.py - calling functions after adding decorator

As you can see, adding this magic greeting_decorator before every function definition did the job!

In this article you will learn what is this greeting_decorator, how it works and how to implement one.


Function object understanding

Let's start from a deeper understanding of function object. Yes, I'm talking about the old good function like this:

decorator_6.png

Let's discuss some properties of function objects.

Function can be called

The most straightforward usage of a function is of course calling it, like this:

decorator_7.png

Note that function call is executed when you provide round brackets after the function identifier: greet()


Function is a regular python object

In addition to actually calling the function and running it's code, we can also access the function object itself, print it, check it's type, and even access it's unique properties:

decorator_8.png

Note that when we access function as an object, we do not put round brackets () after the function identifier! Also note some useful function attributes used in the snippet above.


Function can be passed as a parameter

Since function is a regular object, it can be passed as a parameter to another function, like any other object. The following code snippet demonstrates this:

decorator_9.png

Here we define a new function wrapper that receives as parameter another function other_function and calls this other_function (which is actually our greet function - hence you see "Hello All!" displayed)!


Function can create another function and return it.

Only one step left before we implement our first decorator. Since we saw that function is actually a regular python object, it can be defined inside a function (just like you define any other variable inside a function). And in addition, function that has been defined inside another function can also be returned from that function.

decorator_10.png

In the code snipped above we define an "internal" function display_quote inside a function create_quote. After its creation, display_quote function is returned to the caller.

So, create_quote is a function that receives no parameters and returns a function that displays a very nice quote. Examine the code snippet below and note that return value of create_quote is function! Also note that calling create_quote does not print the quote, it only creates a function that will print the quote the moment it will be called.

decorator_11.png

Now, since created_function variable is a function object (that knows how to print a nice quote once its called), we can call it, and the output will be as follows:

decorator_12.png


OK, now you are ready to implement your first python decorator! In fact we will implement greeting_decorator that you saw in our motivating example.

Decorator implementation

So what is decorator? Decorator is a function that receives as parameter another function. Then decorator defines an inner function that performs various actions, among which it calls the function that has been received as a parameter. Then the inner function is returned to the caller.

In order to implement the logic for our motivating example, we need to create some kind of "wrapper" function that will receive as parameter one of our original functions and will perform the following:

  1. Print Welcome message
  2. Call the original function
  3. Print Good-Bye message

After we have this new "wrapper" function created, we would like to replace the original function with the new "wrapper" function, such that not the original, but the "wrapper" function will be called when needed. In this way, instead of running our original function (that does not display welcome and good-bye messages), we will run an enriched "wrapper" function that not only runs the original code, but also prints beautiful messaging at the beginning and in the end.

Decorator is a function which task is to create this "wrapper" function and return in to the caller (python interpreter), that in turn will replace the definition of the original function with the "wrapper" function definition returned from the decorator.

So our greeting_decorator will be defined as follows:

decorator_13.png

As you can see, greeting_decorator creates a function wrapper_func that performs 3 steps discussed above. After definition, wrapper_func is returned from the greeting_decorator.

Then, in order to use this decorator you can decorate any function with our greeting_decorator like this:

decorator_14.png

This special greeting_decorator syntax signals python interpreter that factorial identifier should be defined as follows:

# pseudo-code
factorial = greeting_decorator(factorial)

So instead of storing factorial as an identifier that points to the code defined inside it, factorial is assigned to another function, our "wrapper" function that is returned by our greeting_decorator. And since factorial identifier now points to the "wrapper" function, each time factorial() will be called, it will actually run our enriched code implemented in the "wrapper" function.

That's why after decorating our factorial function with greeting_decorator, the original factorial behavior will be changed so it will print messages before and after running the original factorial code. Python decorator secrets are now revealed to you!


I hope now you have a better and deeper understanding of python decorators.

I encourage you to run code from this article by yourself to make sure everything is clear. You can take the code from my GitHuband Google Colab.

Continue learning Python decorators in part 2

Subscribe to my channel to get notified when the following parts of this Decorators Deep Dive Series will be published (it will happen very soon).