Understanding python decorators

Python decorators, as the name suggests, are decorators for python entities. In simpler terms, it is a wrapper on an existing functionality without modifying that functionality. Now question may arise, why do we need decorator? Imagine a scenario where you are accessing a function from third party library and want to provide it some additional functionality but do not want to modify the existing function. Here comes decorator in picture. A decorator can be used by using @ annotation followed by decorator name.

A worth mentioning information about decorators is that they are callable that take callable as an argument and return a callable. A callable in python is anything that can be called. For example, a class, a method, etc. are callable.

Why to use decorator?

Let us use a practical scenario to use decorator. Imagine you have different functions in an application. Somehow your application is performing badly. First step to investigate the issue is by identifying the piece of code that is taking more time to process its responsibilities. To achieve it, you can add time at the start and end of method and then difference of two time objects will give execution time of that function. Simple, isn’t it? Now imagine you have 50 such functions where this modification is needed. You can always put logs separately for each function, it will work fine. Or decorator can be used that will decorate those functions with a execution time decorator. Let’s see how it can be implemented with a small example.

def my_decorator(input_function):
    def inner_decorator():
        print "decorator start"
        input_function()
        print "decorator end"
    return inner_decorator

def function_x():
    print "I am not using decorator"

@input_function
def function_y():
    print "I am using decorator"

When function_x and function_y are called, different outputs are observed:

>>> function_x()
I am not using decorator

>>> function_y()
decorator start
I am using decorator
decorator end

function_y was using decorator and hence we see different outputs.

If we see my_decorator, it is taking a function as an input(which is a callable) and returns inner_decorator, which again is a callable. To log a method’s execution time, we can create a decorator like this:

>>> def log(f):
...    def decorated_function():
...       from datetime import datetime
...       print "Execution started at ", datetime.now()
...       f()
...       print "Execution completed at ", datetime.now()
...    return decorated_function
...
>>> @log
... def print_odd_numbers():
...    for i in range(20):
...       if i % 2 == 1:
...          print i,
...
>>> print_odd_numbers()
Execution started at  2016-08-23 16:17:11.965000
1 3 5 7 9 11 13 15 17 19 
Execution completed at  2016-08-23 16:17:11.967000

So whichever function we want to log with execution time, it can be decorated with log decorator without explicitly adding logs for each function.

Decorating function that has arguments

All the above examples depict a decorated function that doesn’t take any arguments but that may not be the case every time. Function arguments can be handled with a slight change in the above example.

>>> def log(f):
...    def decorator(*args, **kwargs):
...       from datetime import datetime
...       print "Execution started at ", datetime.now()
...       f(*args, **kwargs)
...       print "Execution completed at ", datetime.now()
...
>>> def log(f):
...    def decorator(*args, **kwargs):
...       from datetime import datetime
...       print "Execution started at ", datetime.now()
...       f(*args, **kwargs)
...       print "Execution completed at ", datetime.now()
...    return decorator
...
>>> @log
... def odd_numbers(start, end):
...    for i in range(start, end + 1):
...       if i % 2 == 1:
...          print i
...
>>> odd_numbers(1, 10)
Execution started at  2016-08-23 16:40:47.256000
1
3
5
7
9
Execution completed at  2016-08-23 16:40:47.258000

Here we have used *args for arguments and *kwargs for keyword arguments. So, any number of arguments that are expected by decorated function will be passed inside the decorator.

The post Understanding python decorators appeared first on PyJournal.