Python decorators are used to add further functionality to a Python function without explicitly modifying the code of the original function. Consider a scenario where you want your function to perform either Task A or both Task A and Task B, depending on certain conditions. In a case like this, you can define a function that only performs Task A and then you can create a decorator that performs Task B. Whenever you want your function to perform both Task A and Task B, you can extend your Task A function using the decorator that performs Task B. When you want your function to only perform Task A, simply execute the function without the decorator.

Before we show you how to create Python decorators, you need to know:

  1. How to store a function in a variable,
  2. How to nest a function inside another function,
  3. How to pass functions as parameter values to other functions, and
  4. How to return a function from another function.

We’ll study these concepts in this tutorial by stepping through a few examples. Once you understand these concepts, you’ll see that extending a function using a Python decorator is extremely straight-forward.

Storing and Calling Functions via Variables

Believe it or not, you can store a function inside a normal Python variable and can even call the function using the variable. Let’s see this with an example. The following script defines a function my_func() that prints a simple statement on the console.

def my_func():
    print("Hello how are you?")

The following script is the normal way you would call the function:

my_func()

Output:

Hello how are you?

Instead of calling the function via a function call, though, if you simply write the function name and execute the script you will see the function name printed on the console.

my_func

Output:

<function __main__.my_func()>

Because of this, a function can be stored in a variable using the function name (not the function call). The following script stores the my_func() function in a variable func_var. Next, the my_func() function is called using the func_var variable. In the output, you’ll see the output of the code executed when the my_func() is called.

func_var = my_func
func_var()

Output:

Hello how are you?

The output from the above script shows that you can store a function in a variable and you can also call the function via the variable you used to store the function name. This is an important concept for decorators as we’ll soon see.

Nested Functions

Did you know you can wrap a function inside the body of another function? This is called nesting a function. In the following script, you have two functions:outer_func() and inner_func(). The inner_func() function is defined inside the outer_func function and can only be accessed inside the outer_func function.

def outer_func():
    print("This is inside the outer function")

    def inner_func():
        print("This is inside the inner function")

Let’s try to call the outer_func() function first.

outer_func()

Output:

This is inside the outer function

The output shows that only the outer_func() function is executed and the script inside the inner_func() function is not executed. This is because the inner_func() function is only defined inside the outer_func() function and is not called inside the outer_func() function.

Modify the outer_func function so that it calls the inner_func() and see what happens:

def outer_func():
    print("This is inside the outer function")

    def inner_func():
        print("This is inside the inner function")

    ## calling inner function inside the outer function
    inner_func()

Now call the outer_func() function as shown below:

outer_func()

Output:

This is inside the outer function
This is inside the inner function

The output now shows the output of the inner_func() function as well since it’s explicitly called inside the outer_func() function.

Functions Returning other Functions

Our third concept is demonstrating a function can return another function in the same way that it returns any other object. For instance, in the following script, the outer_func() function returns the inner_func() function using the keyword return.

def outer_func():
    print("This is inside the outer function")

    def inner_func():
        print("This is inside the inner function")

    return inner_func

This is important since it means the returned function can be stored inside a variable. In the following script, the outer_func() is called and the value returned by the outer_func() is stored in the func_var2 variable.

func_var2 = outer_func()
func_var2

In the output below, you see two lines. The first line is basically the string printed when the outer_func() function is called. The second line prints the value of the func_var2 variable which basically contains the reference to the inner_func() function returned by the outer_func() function.

Output:

This is inside the outer function
<function __main__.outer_func.<locals>.inner_func()>

Using the func_var2 variable, you can now call the inner_func() function as shown in the following script:

def outer_func():
    print("This is inside the outer function")

    def inner_func():
        print("This is inside the inner function")

    return inner_func

func_var2 = outer_func
func_var2()

Output:

This is inside the inner function

By combining the 3 concepts we’ve discussed so far, we’ve shown you how to explicitly call a function nested inside another function by storing the outer function, which is constructed to return the nested inner function, as a variable and calling that variable. We have one more concept to introduce before we describe Python decorators.

Passing Functions as Parameters to Other Functions

You can pass functions as parameters to the other function in the same way that you pass other objects as function parameters. Here’s an example. In the script below, you define a function named outer_func() which accepts one parameter function_param. Inside the outer_func() a string is printed and then the function passed as a parameter value is called.

def outer_func(function_param):
    print("This is inside the outer function")

    function_param()

Let’s define a simple function, my_func(), that we’ll pass as a parameter value to the outer_func() function.

def my_func():
    print("This function is passed to a function parameter")

Now if you pass the my_func() function as a parameter to the outer_func() function, you’ll see that the outer_func() first prints a string and then calls the my_func() function passed to it as a parameter. Let’s put it all together with this script:

def outer_func(function_param):
    print("This is inside the outer function")

    function_param()

def my_func():
    print("This function is passed to a function parameter")

outer_func(my_func)

Here’s the output of the script above:

Output:

This is inside the outer function
This function is passed to a function parameter

Now that you’re familiar with the four concepts needed to create a Python decorator, let’s show you how to define a decorator in Python.


Get Our Python Developer Kit for Free

I put together a Python Developer Kit with over 100 pre-built Python scripts covering data structures, Pandas, NumPy, Seaborn, machine learning, file processing, web scraping and a whole lot more - and I want you to have it for free. Enter your email address below and I'll send a copy your way.

Yes, I'll take a free Python Developer Kit

Defining a Python Decorator Function

To create a Python decorator, you need to define a function with one parameter. The function you need to extend using the decorator is passed as a parameter to the decorator function. Inside the function, you need to define a wrapper function. When you extend an original function using a decorator and then call the original function, the script inside the wrapper function of the decorator function executes instead of the script inside the original function. This will all make more sense once we walk through our next example.

In this example, our script creates a Python decorator my_decorator(). Inside the my_decorator() function, you define another function wrapper_function() which acts as a wrapper function. The script that modifies the original function, function_to_be_decorated(), is passed as a parameter to the my_decorator() function, which is called inside the wrapper_function(). In the following script, you simply add a line before and after the call to the original function. The wrapper_function() is then returned by the my_decorator() function.

def my_decorator(function_to_be_decorated):

    def wrapper_function():
        print("This is some code before the decorated function")

        function_to_be_decorated()

        print("This is some code after the decorated function")

    return wrapper_function

Extending a Function with a Decorator Function

Now that you’ve defined your decorator, you need to apply it to the function you want to extend. Let’s define a simple function my_func() that prints a string on the console.

def my_func():
    print("This is the function to be decorated")

my_func()

Output:

This is the function to be decorated

You’ll extend the my_func() function using the my_decorator() decorator function you defined in the last section. To decorate a function, simply pass the function to the decorator as a parameter as shown in the following script.

decorated_function = my_decorator(my_func)

What kind of output do you think we’ll get when we call the decorated_function(), like we do at the end of this combined script?

def my_decorator(function_to_be_decorated):

    def wrapper_function():
        print("This is some code before the decorated function")

        function_to_be_decorated()

        print("This is some code after the decorated function")

    return wrapper_function

def my_func():
    print("This is the function to be decorated")

decorated_function = my_decorator(my_func)

decorated_function()

The output below shows the output of the wrapper_function() you created inside the my_decorator() function is printed. You can see that the functionality of the my_func() function has now been extended and two additional print statements are printed in the output of the function.

Output:

This is some code before the decorated function
This is the function to be decorated
This is some code after the decorated function

The script we just showed is the most important example in this entire tutorial. With it, you can now call my_func() to only print the one print statement inside my_func(), or you can call decorated_function() to print the fully extended version of my_func(), which prints all three print statements.

If you always your script to be extended, there’s an easier way to decorate a function. Simply write the decorator name using the “@” symbol before the function definition as shown below. In the following script, the functionality of the my_func() function is extended using the my_decorator() function.

@my_decorator
def my_func():
    print("This is the function to be decorated")

Now when you call my_func(), the function is automatically extended using the my my_decorator() function:

def my_decorator(function_to_be_decorated):

    def wrapper_function():
        print("This is some code before the decorated function")

        function_to_be_decorated()

        print("This is some code after the decorated function")

    return wrapper_function

@my_decorator
def my_func():
    print("This is the function to be decorated")

my_func()

Instead of printing the one print statement inside the my_func() function, the output shows all three print statements are printed:

Output:

This is some code before the decorated function
This is the function to be decorated
This is some code after the decorated function

Finally, you can also extend multiple functions using a single decorator. For instance, in the following script the my_decorator() decorator extends the functionality of another function, my_func2().

def my_decorator(function_to_be_decorated):

    def wrapper_function():
        print("This is some code before the decorated function")

        function_to_be_decorated()

        print("This is some code after the decorated function")

    return wrapper_function

@my_decorator
def my_func():
    print("This is the function to be decorated")

@my_decorator
def my_func2():
    print("This is another function to be decorated")

my_func2()

Output:

This is some code before the decorated function
This is another function to be decorated
This is some code after the decorated function

This pretty neat, isn’t it? Decorators give your functions the flexibility to perform a single task or multiple tasks without forcing you to repeat lines of code. With decorators, you’re able to perform either Task A or both Task A and Task B, while still following the DRY principle of programming (Don’t Repeat Yourself). For more Python tips like this, please subscribe using the form below!


Get Our Python Developer Kit for Free

I put together a Python Developer Kit with over 100 pre-built Python scripts covering data structures, Pandas, NumPy, Seaborn, machine learning, file processing, web scraping and a whole lot more - and I want you to have it for free. Enter your email address below and I'll send a copy your way.

Yes, I'll take a free Python Developer Kit