Understanding Python Decorators

If you have ever wondered what those @somethingmean above a python function or method then you are going to have your answers now. This @something line of code is actually called a decorator. I have red from various articles about them but some of them were not able to clarify the concept of a decorator and what we can achieve with them. So in this post we'll learn a lot about python decorators. Here is a list of topics we'll be covering.

What is python decorator

A python decorator is nothing but a function which accepts your given function as a parameter and returns a replacement function. So its like something this

def decorator(your_func):
    def replacement(your_func_args):
        #do some other work
    return replacement

@decorator
your_func(your_func_args):
    #your_func code

Now when your_func gets called then the function which actually gets executed will be replacement. Note that the parameter pattern of the replacement function must be same to that of your function. To understand what is happening, lets read the next section.

Understanding the core concept

It is very common in python to assign function objects, to pass function objects as arguments and to call functions using function refernces. What all these mean is you can do all of the following in python.

def any_func():
    #do some work

your_func = any_func

your_func()
Python decorators are based on the above concept and using a python decorator is equivalent to writing the following code.
def decorator(func):
    def replacement(args):
        #do some other work

    return replacement

def your_func(args):
    #do some work

your_func = decorator(your_func) #assign the replacement function to your given function. So now your_func points to replacement
your_func(args) #call the replacement function
As you can see, a decorator does nothing special, it just overrides your given function definition. And whenever your_func will be called then replacement will be executed. Lets see a simple example now which is very helpful while understanding decorators.
def encoder(func):
    def decorator(html_string): #function which replace < and > with html codes and calls the passed in function for formatting
        html_string = html_string.replace(r'<', r'&lt;')
        html_string = html_string.replace(r'>', r'&gt;')
        result = func(html_string)
        return result
    return decorator

@encoder
def para_wrapper(html_string): #our function which simply wraps the html string with a paragraph tag
    return '<p>{0}</p>'.format(html_string)
In this example what is happening is we use a decorator @encoder which replaces the function para_wrapper with decorator. Just remember that before the decorator assigns the new function to your function it gets the original copy of the function via the func argument. So we can call our original function using that reference. Now look what decorator is doing. It firstly makes the replacements in the html_string and then call the original function for its formatted output because that is what we want to send to the caller. Then it returns the output which our function generates for the new html_string. It is one of the use of the decorators. We'll see in the later section what other purposes can a decorator be used for.

Multiple decorators(Nested)

Decorators can easily be nested for multi-level uses. Nesting the decorator simply means nesting the function references.Lets see how nested decorator look

@decorator3
@decorator2
@decorator1
def your_func(args):
    #do some work

#This is equivalent to the following code

your_func = decorator3(decorator2(decorator1(your_func)))
Nested decorators are helpful when there is one decorator which you can't modify and you want to add some input/output formatting or something then the easy way to do this is to add one more decorator.

Class method decorators

Class method decorators are used for class methods. These decorators are not any special ones. As i mentioned earlier that the parameter list of the replacement function must be same as the given function, in a class method decorator the first parameter of the replacement function is self which is the reference to the current object so you can use current objects properties inside the replacement function if you want. All other things are the same. Lets see some skeleton code

def greet(func):
    def replacement(self):
        return 'Hello'+func()
    return replacement

class Person(object):
    def __init__(self, args):
        #initialisation

    @greet
    def name(self):
        return self.firstname +' '+ self.lastname

Where can we use decorators

Python decorators can be used for a lot of scenarios. Some of them are

  • Input Verification
  • Input modification
  • Output Modification
Lets see each of the use case one by one. I will show you the code skeleton only. Input Verification Suppose you want to verify the input arguments before executing the main function. Now you can write the verification code inside your main function also but suppose there are a lot of functions which require input verification then it is smart decission to write a function to do that. This is how our code will look.
def your_decorator(your_func):
    def replacement(your_func_args):
        #do the input verification work.
        if(verification_pass):
            return your_func(your_func_args)
        else:
            return #You can return anything you want like an error message or raise an exception

    return replacement

@your_decorator
def your_func(your_func_args):
    #do what you want with the verified input
Input Modification: We have already seen an example of this in the previous section where we wrote a decorator to replace the < and > symbols with html codes.
Output Modification Now suppose you want to modify the output returned by your function. For example, your function returns a data dictionary but you want to add some default keys to the data dictionary before returning the output. Something like in a client-server session where you want to add session id and expire time in the data dictionary. Now you can add those fields right in your function but as the number of function grows this becomes tedious and we won't want to repeat the code. So lets write a decorator to add the data to the output of your function.
def response(func):
    def decorator(func_args):
        result = func(func_args)
        result['session_id'] = key
        result['expire_time'] = time
        return result

    return decorator

@resposne
def your_func(your_func_args):
    data = {}
    #fill data dictionary
    return data
These were some simple cases where we can use decorators. There are some advanced uses of decorator also but those are only the modification of the above cases.

Comments

Post a Comment

Comment on articles for more info.

Popular posts from this blog

Search box for blog or website

Image Search Engine Using Python

Cordova viewport problem solved