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:
- How to store a function in a variable,
- How to nest a function inside another function,
- How to pass functions as parameter values to other functions, and
- 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 my_func()
function is called using 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 = 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 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 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 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.
Code More, Distract Less: Support Our Ad-Free Site
You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.
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!
Code More, Distract Less: Support Our Ad-Free Site
You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.