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

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

  • Valeria Aynbinder
  • Coding
  • 12 Jul, 2024

This is a 2-Part Series:


A decorator is a special feature in Python that lets you modify the behavior of existing functions without changing their code. Yes, you read that right — you can change functions without actually altering their code! This is what makes decorators so powerful and useful in Python. In this article, we will explore what decorators are and how you can use them to make your code cleaner and more efficient.

By the end of this guide, you'll know what a decorator is, how it works, and how to make your own. Along the way, you might learn some new things about functions too! 😊 Decorators are often used for things like logging, checking permissions, or adding functionality in a clean way. Let’s get started!

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

Motivating Task

Let’s start with an example that shows why we need decorators.

Imagine you have a Python module called various_functions.py that includes several useful functions like calculating a factorial, capitalizing a name, and converting temperatures. Each of these functions takes input from a user, performs its calculations, and prints the result:

various_functions.py module

If we run these functions, we get the following output:

main.py — calling functions with output

The module works fine, but I want to make it more user-friendly. I want every function to first print a welcome message when it starts and then print a goodbye message before it finishes. This way, users have a better experience.

The simple but tedious way to do this is to modify each function to add the welcome and goodbye messages manually. However, that would mean repeating the same code for every function. Plus, if I ever add new functions in the future, I'd have to remember to add those messages again. This is a lot of repetitive work, which makes the code harder to maintain.

But here's the good news: Python decorators can solve this issue in a much better way. By using a decorator, I can add the exact behavior I need without touching the core logic of each function. Let’s take a look at how decorators make this possible (note the @greeting_decorator below):

various_functions.py with decorators

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

main.py — calling functions after adding decorator

As you can see, adding @greeting_decorator before each function did the job! With just one line, we added a welcome and goodbye message to each function without changing the core function code.

In this article, you will learn how the @greeting_decorator works and how to create your own decorators. By the end, you'll understand how decorators can help you write cleaner and more readable code.

Understanding Function Objects

Let’s start by understanding functions a little better. Functions in Python are more than just blocks of code — they are actually first-class objects. This means you can use them like any other object. You can assign them to variables, pass them as arguments, and even return them from other functions.

Here’s an example of a simple function:

greet function definition

Functions Can Be Called

The most common way to use a function is by calling it with parentheses. For example:

greet function definition

When you add parentheses greet(), you are actually executing the function. If you use just greet without parentheses, you're referring to the function itself.

Functions Are Regular Python Objects

In Python, functions are objects, just like strings or numbers. You can assign a function to a variable, check its type, or even print it:

printing greet function attributes

Note: When accessing a function as an object, don't use parentheses. You’re not calling the function — you're just referring to it.

Because functions are objects, you can also add attributes to them, which can be very useful in some cases. This makes functions in Python very flexible.

Functions Can Be Passed as Parameters

Since functions are objects, they can also be passed as arguments to other functions. This is really powerful, as it allows you to extend or change the behavior of a function without modifying it directly. Here’s an example:

passing greet function as parameter to wrapper function

In this code, we have a wrapper function that takes another function (other_function) as a parameter. It calls other_function (which is our greet function), which is why you see the output “Hello All!”. Passing functions as arguments like this is very useful for creating decorators because it allows us to wrap additional behavior around a function.

Functions Can Create and Return Other Functions

The final concept to understand before we make our first decorator is that functions can also be created inside other functions and returned. Here’s an example:

display_quote is defined inside create_quote and returned

In the example above, display_quote is defined inside create_quote. The function create_quote doesn’t just run the inner function — it returns it, which allows us to call display_quote later.

Now, let’s look at how we can use the create_quote function:

object returned from create_quote() is a function

The created_function variable is actually a function, and when we call it, it prints the quote:

Calling create_function that has been returned from create_quote function

This ability to create and return functions is what makes decorators so powerful. By wrapping one function around another, we can modify its behavior in a clean and reusable way.

OK, now you’re ready to create your first Python decorator! We will create the greeting_decorator that you saw earlier.

Implementing a Decorator

So, what exactly is a decorator? A decorator is a function that takes another function as an argument, defines an inner function that adds some behavior, and then returns that inner function. This might sound complicated, but it’s not too difficult when you see it in action.

Here’s what we want our decorator to do:

  1. Print a welcome message.
  2. Call the original function.
  3. Print a goodbye message.

We want this behavior to be added to our original functions without modifying them. Instead, we’ll use a decorator to wrap the original functions with this new behavior.

Here’s how we implement the greeting_decorator:

greeting_decorator definition

In greeting_decorator, we define an inner function called wrapper_func that prints a welcome message, calls the original function (func()), and then prints a goodbye message. Finally, we return wrapper_func.

To use the decorator, we just need to add @greeting_decorator before our function definition, like this:

Decorating factorial function

This @greeting_decorator syntax is a shortcut for writing:

# Pseudo-code
factorial = greeting_decorator(factorial)

Instead of pointing factorial to its original code, it now points to the wrapper_func returned by greeting_decorator. So, when we call factorial(), we are actually calling the wrapper function that adds the extra behavior.

That’s why, after decorating our factorial function with @greeting_decorator, the function now prints messages before and after running the original factorial code.

Decorators are very helpful in Python. They let you change or extend the behavior of functions without modifying the original code. This is especially useful for tasks like logging, validation, or access control, which can be used across many functions without repeating code.

Conclusion

I hope this guide has given you a solid understanding of Python decorators. By using decorators, you can write cleaner, more reusable, and more maintainable code.

Feel free to try out the code examples yourself to see how decorators work. You can find the code on my GitHub and Google Colab.

Decorators are often seen in frameworks such as Flask and Django, where they are used for tasks like request handling, access control, and setting up middleware. Their flexibility allows you to write cleaner, more efficient code that’s easy to read and understand.

Continue learning Python decorators in part 2

Tags :
Share :

Related Post

Python Decorators - Deep Dive in 2 Parts (2/2)

Python Decorators - Deep Dive in 2 Parts (2/2)

  • Valeria Aynbinder
  • Coding
  • 17 Jul, 2024

This is a 2-Part Series:Part 1: Intro + Build Your First Decorator Part 2 (current): Decorators for functions that receive parameters an

Read More
Django Manage Commands Cheat Sheet

Django Manage Commands Cheat Sheet

  • Valeria Aynbinder
  • Coding
  • 12 Aug, 2024

Installations In order to get started with Django, all you need to do is install Django package using pip: pip install djangoAfter the installation is complete, you can move forward and

Read More
Python Sets — A Powerful Collection Every Developer Should Use

Python Sets — A Powerful Collection Every Developer Should Use

  • Valeria Aynbinder
  • Coding
  • 10 Sep, 2024

Introduction There are three basic Python collections: list, dictionary, and set. While list and dictionary are widely used in almost every piece of Python code, the set sometimes

Read More
Google Colab - a must-have tool for Developer and Data Scientist

Google Colab - a must-have tool for Developer and Data Scientist

13 amazing features and usage tips.What is Google Colab?Colaboratory, or “Colab” for short, is a product from Google Research. Colab allows an

Read More
Python Regular Expressions - Cheat Sheet

Python Regular Expressions - Cheat Sheet

  • Valeria Aynbinder
  • Coding
  • 16 Oct, 2024

Many code examples + useful tips. Bonus added to the end of the post.Regular expressions is an extremely useful tool, and like any developer, I use it a lot when working with texts. Since I al

Read More