How to Use Python Decorators and Why?

In this Python article, we will discuss decorators, their uses, and how to use decorators using few examples. Let’s begin.

1. What is a Decorator?

The modification of properties and behavior of a class or a function can be extended according to the requirement, such modification is made with the help of Decorators.

By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

With the help of decorators, one can easily wrap any other function for extending the behavior and properties of the wrapped function, these modifications are not permanent.

This is also known as metaprogramming as it involves the modification of one part of the program with the help of another part of the program during the time of compilation.

Syntax:
@any_decorator 
def decorator_name():
     statements 

'''Above code is similar to: 
def decorator_name():
     statements      

decorator_name = any_decorator(arguments)'''

The syntax shows the use of any_decorator as a function which can be called, on the top of any other callable function, it will add decorator_name function and it returns the wrapper function.

It is important to note that decorators do not change the behavior of the decorated function, it simply adds some functionality on top of the existing one.

Let’s discuss some of the important concepts before going deep into the decorators which will help in understanding decorators better.

1.1. What is meant by First class Object?

We term any function as a first-class object when the function can be used or passed as arguments.

Characteristics of first-class functions:

  • The function will be an object type instance.
  • The function can be stored inside a variable.
  • The function can be passed to other function as a parameter.
  • The function can be returned from a function.
  • The function can be stored in different data structures (hash tables, lists,etc).

1.2. Function as an Object

Let’s take an example to understand the function as an object in python.

# function as objects 
def readout(message): 
  return message.lower() 
  

read_me = readout  
print(readout('CODINGEEK!'))   
print(read_me('CODINGEEK!')) 
Output
codingeek!
codingeek!

The above example demonstrates the assigning of the function readout to a variable read_me. This tells us that functions in Python are an object like everything else (Yes, that’s correct everything in Python is an Object).

Here read_me and readout are just two different names(variables) pointing to the same object.

1.3. Function as an Arguments

Let’s take an example to understand function as arguments.

#function passed as argument to another function 

def readme_soft(message): 
  return message.lower() 
  

def readme_loud(message): 
  return message.upper() 


def positive_message(inp_func): 
  # function is stored in variable 
  print("Decorating the function..")
  pm = inp_func("Good news, you are learning python advance.") 
  print (pm) 


positive_message(readme_loud) 
positive_message(readme_soft) 
Output
Decorating the function..
GOOD NEWS, YOU ARE LEARNING PYTHON ADVANCE.
Decorating the function..
good news, you are learning python advance.

The above example illustrates positive_message is taking another function readme_loud and readme_soft as parameters. We also extended the functionality of method positive_message, which means we decorated that message.

Inside the function positive_messge, we call the functions which are passed as arguments.

1.4. Return Function from Function

This time, let’s take one more example to understand the returning of the function to another function or Python Closures.

# Functions can return another function
def subtract(val1):
  def sub(val2):
    return val1-val2

  return sub


sub_val = subtract(174)
print(sub_val(121))
Output
53

Finally, we have understood the important concepts using the three programs which were prerequisites to understand decorators.

Let’s discuss decorators now.


2. Using Decorators

We have discussed that decorators can add additional behavior to the existing method.

Now, let’s take an example to understand how does it actually work.

def new_decorator(inp_function):      # decorator definition
  '''Here inside_decorator is a Wrapper function 
     where the argument will be passed'''

  '''local functions can be accessed by inner function 
       here example "inp_function'''

  def inside_decorator():
    print("I am inside the decorator")

    # calling actual function inside the wrapper function.
    inp_function()       

    print("I have successfully made my execution")

  return inside_decorator


# function, for being called inside wrapper
def calling_function():
  print("This is the main content!")


# passing 'calling_function' inside decorator for controlling its behavior
calling_function = new_decorator(calling_function)

# function call
calling_function()
Output
I am inside the decorator
This is the main content!
I have successfully made my execution

In the above example, first the passing of calling_function will run, it will call the new_decorator followed by inside_decorator and then directly returning the inside_decorator.

Now the second calling_function will run and all the functions inside the new_decorators along with the inp_function and all the remaining print statements will be executed and the program will finish.

2.1. Decorator with Generic Method Arguments

There must be a question in mind, can the decorator find the time of execution? Well, the answer is ‘Yes’, we can use a decorator to find the execution time. This is just one real-world example and there can be a lot more.

Let’s understand how to calculate the method execution time with an example.

import time


def getme_time(inp_function):
  """decorator to calculate time duration for execution of function."""

  def inside_decorator(*args, **kwargs):
    """
    added arguments inside the inner1,
    if function takes any arguments,
    can be added like this.
    """
    start_time = time.time()

    inp_function(*args, **kwargs)

    stop_time = time.time()
    print("Time taken for execution : ",
          inp_function.__name__, stop_time - start_time)

  return inside_decorator


@getme_time
def multiply(value1, value2):

  time.sleep(1)
  value = value1 * value2
  print(value)


# function call
multiply(12, 5)
Output
60
Time taken for execution :  multiply 1.0012080669403076

In the above example, you may notice a keen difference in the parameters of the inner function. The inner function takes the argument as *args and **kwargs which means that a tuple of positional arguments or a dictionary of keyword arguments can be passed of any length. This makes it a general decorator that can decorate a function having any number of arguments.

If we do not want to use *args and **kwargs then we have to keep the arguments the same both for the decorated function and the nested function of the decorator.


3. Chaining of Dcorators in Python

Python allows users to chain multiple decorators.

In simpler terms, it means that we can decorate one function multiple times with the same or different decorator(s) as per the requirement and use.

This can be done by simply putting the decorator above any particular function.

The benefit of using the chaining method is that it makes the decorators more useful for developing reusable blocks as it collects different effects together.

Chaining multiple decorators is also known as nested decorators in Python.

def first_decorator(func_name):
  def inside_function():
    print("First Decorator")
    func_name()
  return inside_function


def second_decorator(func_name):
  def inside_function():
    print("Second Decorator")
    func_name()
  return inside_function


@first_decorator
@second_decorator
def passvalue():
  print("Decorated function")


passvalue() 
Output
First Decorator
Second Decorator
Decorated function

In the above program, the function passvalue pass value 5 to both the defined function that is first_decorator and second_decorator.

The function called is equivalent to first_decorator(second_decorator(passvalue())) and hence the order of annotations matter.


4. Conclusion

Finally, if we sum up, in this article we covered everything about decorator which was necessary and important along with different program implementation, we have covered:

  • What is a Decorator and why do we need a decorator in Python.
  • What is meant by First class Object in python?
  • How can we use Decorators?
  • How to make chaining of decorator in Python?

Helpful Links

Please follow the Python tutorial series or the menu in the sidebar for the complete tutorial series.

Also for examples in Python and practice please refer to Python Examples.

Complete code samples are present on Github project.

Recommended Books


An investment in knowledge always pays the best interest. I hope you like the tutorial. Do come back for more because learning paves way for a better understanding

Do not forget to share and Subscribe.

Happy coding!! ?

Recommended -

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x
Index