Sunday, 24 May 2020

Decorators in Python

Background

In the last post, we saw what are closures in Python. In this post, we will see what are decorators in Python. Closures are heavily used by Decorators. Let's see how. (You can also check my youtube video).



 Decorators in Python

As we know already everything in Python is an Object. A decorator is an object that is used to modify a function or a class. The function decorator takes a reference to the decorated function and returns a new function. It will internally call the actual decorated/referenced function.

You can also have a class as a decorator instead of a function. When we decorate a method with a class, that function becomes an instance method of that class.

In either case, the original code is not changed. 

Let us say we have a function that makes a remote API call and we want to monitor the start and end time for that API. You have a method as follows:

def make_api_call(*params):
    # Simulate API call
    time.sleep(3)
    print("Done")


I have just added a sleep of 3 seconds to simulate the actual API call. So now if we want to monitor the start and end time of this API, we can modify this function to print time before and after the API call, but it is not correct to modify existing functions. This is where the decorator comes into the picture.

from datetime import datetime
import time

def monitor_performance(func):
    
    def wrapper_func(*params):
        print("Making API call with params {} at {}".format(params, datetime.now()))
        func(params)
        print("Finishing API call with params {} at {}".format(params, datetime.now()))
              
    return wrapper_func

@monitor_performance
def make_api_call(*params):
    # Simulate API call
    time.sleep(3)
    print("Done")
              
make_api_call("param1", "param2")



When you execute this code it prints the following output.
Making API call with params ('param1', 'param2') at 2020-05-24 22:31:19.480543
Done
Finishing API call with params ('param1', 'param2') at 2020-05-24 22:31:22.484090



You can notice how we decorated our actual method make_api_call with a custom decorator monitor_performance. Also, you must have noticed our decorator function used a closure - another internal method called wrapper_func to actually monitor start and end time.

As I mentioned before the decorator can be used to modify the actual method and internally calls the actual method. In this case, before we call the actual method we print start and end time.

You would have also noticed that the parameters passed to make_api_call are automatically passed to our wrapper function as we are returning this wrapper function from the decorator. Also, notice how we have declared decorator for our function using '@' notation.


Using class instead of function for a decorator


The same code can be used with a class as a decorator. As we already know everything in Python is an object and it is callable if it defines the __call__() method.

from datetime import datetime
import time

class monitor_performace:
    def __init__(self, actual_func):
        self.actual_func = actual_func
    
    def __call__(self, *params):
        print("Making API call with params {} at {}".format(params, datetime.now()))
        self.actual_func(params)
        print("Finishing API call with params {} at {}".format(params, datetime.now()))
        

@monitor_performace
def make_api_call(*params):
    # Simulate API call
    time.sleep(3)
    print("Done")
              
make_api_call("param1", "param2")


It prints similar output as before:
Making API call with params ('param1', 'param2') at 2020-05-24 22:39:22.895176
Done
Finishing API call with params ('param1', 'param2') at 2020-05-24 22:39:25.896923





Notes:

  • You can also decorate a class with another class
  • You can chain decorated as well. Each decorated function/class will be called serially.



Related Links








t> UA-39527780-1 back to top