Python

Python Decorators

In Python, a decorator is any callable Python object used to modify a class or function. It takes a function, adds some functionality to it, and returns it. Decorators are primarily useful when you want to extend any existing function without any modification to the original function. Decorators are commonly used to add functionality to existing functions, such as logging, timing, or authentication.

  • Decorators are called before the function call.
  • It extends the functionality of our original functions without altering their source code.
  • Decorators can be of two types:
    • Function Decorators 
    • Class Decorators

Python Function Decorator Example

Function-based decorators are the easiest way to implement decorators in Python. A function-based decorator takes a function as an argument and returns a new function that wraps the original function.

Let’s create a decorator. First, we create one outer function and another inner function inside it. Here we are creating sample_ecorator the outer function and example_function as the inner function.

This outer function returns the inner function at the end of the outer function and inside the inner function, we will add our functionality before and after a function (that we want to extend or add new functionality, in our case that function is the cube(num) ).

Basically, a python decorator takes a function as input and adds some functionality before and after, and returns it back.

# Create a decorator
def sample_decorator(func):
    # define the inner function 
    def example_function(number):
        print("Before Funciton Call:", func.__name__)
        
        # call original function
        results = func(number)
        print("Cube: ", results)
     
        print("After Funciton Call:", func.__name__)
    # Return inner function
    return example_function

def cube(num):
    return num**3

# decorate the function
decorated_func = sample_decorator(cube)

# call the decorated function
decorated_func(2)
Output:
Before Funciton Call: cube
8
After Funciton Call: cube

Let’s do it in a more compact way. Instead of assigning the function call to a variable, python has the functionality to do this in a simpler and more attractive way by using @ symbol above the function definition with the decorator name.  

def sample_decorator(func):
    def example_function(number):
        print("Before Funciton Call:", func.__name__)
        results = func(number)
        print(results)
        print("After Funciton Call:", func.__name__)
    return example_function

@sample_decorator
def cube(num):
    return num**3

cube(2)
Output:
Before Funciton Call: cube
8
After Funciton Call: cube

In this example, sample_decorator is the name of the decorator function. When the code is executed, the function cube is passed as an argument to sample_decorator, and the decorated function is then called.

Python Class Decorator Example

Decorators have the capability to modify the behavior of any python function or class. Class-based decorators are a more advanced way to implement decorators in Python. In this section, we will define decorator as a class. For defining this, we will use a __call__() method and pass the function to __init__ as an argument. It allows a class instance to behave like a function. For example, consider the following code example that implements a cube decorator using a class:

class CubeDecorator:
 
    def __init__(self, func):
        self.func = func
 
    def __call__(self, *args, **kwargs):
 
        # before function
        print("Before Funciton Call:", self.func.__name__)
        
        # call original function
        cube_value = self.func(*args, **kwargs)
        print("Cube:", cube_value) 
        
        # after function
        print("After Funciton Call:", self.func.__name__)
    
        return cube_value
 
 # Add class decorator
@CubeDecorator
def cube(num):
    return num**3
 
cube(20)
Output:
Before Funciton Call: cube
Cube: 8000
After Funciton Call: cube

After decoration, the call method of the class is called instead of the cube() method.

Real-life Decorator Example

Let’s see one real-life decorator example to calculate the execution time of a function. For example, consider the following code example that implements a execution_time decorator:

from time import time, sleep
 
# Create a decorator    
def execution_time(func):
    
    # define the inner function
    
    def wrapper(*args, **kwargs):
        # get current time before execution
        t = time()
        
        # call function
        func(*args, **kwargs)
        
        # compute time difference
        print(func.__name__, ' function execution time: ', time() - t)
    
    return wrapper

@execution_time
def cube(num):
    return num**3

cube(3)
Output:
cube  function execution time:  2.1457672119140625e-06

The wrapper function of the execution_time decorator calculates the time difference between the before and after the function execution and prints the execution time of the function on the console using the time module.

Summary

Python decorators are the most useful functionality that allows programmers to expand the functionality of a function, class, or method by wrapping it with another function. Decorators mostly used real-life applications such as logging, testing, timing, and authentication. Python has the functionality to add decorators in a simpler and more attractive way by using @ symbol above the function definition with the decorator name.

We can implement decorators using either a function or a class. Function-based decorators take a function as an argument and return a new function that wraps the original function, while class-based decorators use the __call__ method to allow a class instance to behave like a function.