Summary: In this tutorial, we will learn what is a decorator in Python, what are its different types and how can we use it to modify a function.

Introduction to Decorator

A decorator in Python is any callable object (such as a function) that modifies another existing function or class.

To modify, the decorator accepts the function or class as an argument, wraps it with extra python statements, and returns the same.

In Python, everything is an object, even a function.

Consider the following function for instance:

def add(a, b):
    return a+b
    
print(add(5, 6))   #output 11

It take two numbers as arguments and returns their addition result.

Although the add method only seems to only return the sum of two numbers, we can further increase its functionality by passing it as an argument to another function:

def add(a, b):
    return a+b

#decorator function that modifies the passed function
def my_decorator(func):
    #wrap the passed funtion with a new function
    def modified_func(a, b):
        print("Addition started")
        print(func(a, b))
        print("Addition completed")
    #return the new function
    return modified_func

#getting the modified version using the decorator function
modified_add = my_decorator(add)
#executing the function
modified_add(5, 6)

Output:

Addition started
11
Addition completed

In this example, we passed the add function to the decorator function, defined a new function inside of it, and returned the reference of the new function.

We then get the reference of the new function as modified_add=my_decorator(add) and invoke the same.

A function defined inside another function is known as the inner function.

@ Symbol

Python allows to use decorator in very simple way using a @ symbol.

#decorator function that modifies the passed function
def my_decorator(func):
    #define a new function
    def modified_func(a, b):
        print("Addition started")
        print(func(a, b))
        print("Addition completed")
    #return the new function
    return modified_func

@my_decorator
def add(a, b):
    return a+b

add(5, 6)

Output:

Addition started
11
Addition completed

In this example, we have attached @my_decorator to the add method.

So, when we call the function as add(), the new function returned by the specified decorator (i.e. my_decorator) gets invoked instead.

In short, @my_decorator replaces modified_add=my_decorator(add).

Types of Decorators

As we discussed, the decorator is any callable object that can modify an existing function in Python.

And we know that the instance of a class in Python can also be made callable using the __call__ method.

So in Python, we can create two types of decorators:

  1. Decorator using function
  2. Decorator using class

We have already seen the examples of the decorators using function. Let’s see how class decorators work in Python?

Class Decorator

To create a decorator using a class, we have to first make its instance callable.

In Python, we can easily do so using the __call__ method.

The __call__ is a reserved method in Python that when implemented within a class, its instances become callable just like a function (i.e. we can call instances using the ()).

class Connection:
    def __init__(self, url):
        self.url = url
        
    def __call__(self):
        print("Connection Opened")
        print("Acessing URL:", self.url)
        print("Connection Closed")
        

con = Connection("https://youtu.be/lawHlEbYqcc")
con()

Output:

Connection Opened
Acessing URL: https://youtu.be/lawHlEbYqcc
Connection Closed

As you can see that the instance of the Connection class is callable.

When we call the con instance using braces, the __call__ method of the class is invoked.

So now, if we accept the function as a parameter in the __init__ method of the class and modify it in the __call__ method, it will behave like a decorator.

class Connection:
    def __init__(self, func):
        self.func = func
        
    def __call__(self, url):
        print("Connection Opened")
        self.func(url)
        print("Connection Closed")
        
@Connection
def downloadFile(url):
    print("Downloading File:", url)
    
downloadFile("https://youtu.be/lawHlEbYqcc")

Output:

Connection Opened
Downloading File: https://youtu.be/lawHlEbYqcc
Connection Closed

Unlike the function decorator, where we used to define and return an inner function, the __call__ method here behaves as the modified function.

Conclusion

A decorator is any callable object that can modify an existing class or function in Python.

In Python, we can create a decorator either by using a class or by using a function.

The @ symbol in Python eliminates writing boilerplate code to implement a decorator on a function.

Related Topics:

  1. Write custom Python Decorator that accepts arguments

Leave a Reply