Deep dive in Python Decorators

In this post you will learn about python decorators,decorators with arguments and decorator chaining.So lets jump in…

What is python decorators?

Decorators are a way in which we can alter or change behavior of a function.As python treats everything as an object,so we can pass function to the functions and return functions from the functions.This is the basis on which decorators works.Decorators takes a functions as an argument,alter its behavior and returns the output.

Simple Decorator in Python

Suppose I have a function greet() ,which greets everyone with the message “Welcome to python”.Now suppose I want to print “Hi” before this message and “Bye” after this message without making any modification to my original function.Is it possible?Decorators make it possible.Lets take a look.

###Decorator###
def outer(func):
    def inner():
        print("Hiii")
        func()    
        print("Bye")
    return inner

###Normal Function###
def greet():
    print("Welcome to Python")

You can see,there is a greet function which prints “Welcome to python”.Now below it,I have defined a decorator named outer.Remember definition of decorator,it takes function as argument and returns a function. In decorator outer,func is passed as a parameter for decorator .This outer function is returning the inner function.Notice carefully,while writing return inner, I have not written inner(),because we don’t want to call this function.We are just telling python interpreter to go to inner function which is defined in outer function.In inner function actual function executes.inner function will first print “Hi” and then execute the func which was passed as an argument.

Now.we will decorate our greet() function with decorator outer,so that it will print “Hi” before welcome to python.

out=outer(greet)
out()

The above code means,we are first passing greet function to the decorator(outer) function which return a function which is not yet called but ready to be called.This is stored in a variable called out.Then on second line we call the returned function by writing out().Lets see the output.

Output

Hiii
Welcome to Python
Bye

There is a shorter way of decorating the function without using above two lines of code and you may have seen it before.That is done by using “@{decorator-name}” above the function which is to be decorated.

def outer(func):
    def inner():
        print("Hiii")
        return func()    
    return inner
    
@outer
def greet():
    print("Welcome to Python")


greet()

Now,whenever you want to replace “Hiii” with something else,you just need to make change in outer function.

Python Decorators with arguments

What if,we have a function that takes arguments and we want to decorate it.With decorators,we can also decorate function which takes arguments.

Suppose,I have a function info which takes name and salary of a employee and prints it.Now,suppose I want to add 50 bucks in each customers salary and I don’t wan’t to change my original function info,because I wan’t to do it with decorators.

 
def outer(func):
    def inner(e,s):
        s+=50
        func(e,s)    
    return inner
 

@outer
def info(emp,sal):
    print("{} salary is {}".format(emp,sal))

info("Jack",50)

Whatever arguments is passed to the original function,that is passed to the inner function and are accessible inside decorator’s inner function as parameters(e and s).We will change values of original arguments (e and s) and we will pass these changed arguments to original function func,which is a paramters which stores the original function when it is passed to decorator function. As we passe “Jack” and 50 to original function info(),but after decorating it with decorator,we changed salary of Jack and added 50 bucks.So after you run this code,the output should be 100.

Decorator Chaining(Decorator over Decorator)

We can use decorator over a decorator and it is called decorator chaining.Now,suppose we want to print employee name in uppercase even if user pass their name in small letter. We will change it to uppercase using decorator check_case.After changing case to uppercase,we will pass it to decorator outer which will increase salary by 50.

def check_case(func):
    
    def inner(e,s):
            e=e.upper()
            func(e,s)
    
    return inner



def outer(func):
    def inner(e,s):
        s+=50
        func(e,s)    
    return inner

@outer
@check_case
def info(emp,sal):
    print("{} salary is {}".format(emp,sal))

info("JacK",50)

Output:

JACK salary is 70

One thing to notice while chaining the decorators is that,order in which we decorate the function matters.Let’s see this by a different example.

def decor1(func):
    def wrap():
        print("Hi")
        func()
        print("Hi")
    return wrap

def decor2(func):
    def wrap():
        print("Bye")
        func()
        print("Bye")
    return wrap

@decor1
@decor2
def greet():
    print("Welcome to python")

greet()

Output:

Hi
Bye
Welcome to python
Bye
Hi

Now,lets change the order of decor1 and decor2.

def decor1(func):
    def wrap():
        print("Hi")
        func()
        print("Hi")
    return wrap

def decor2(func):
    def wrap():
        print("Bye")
        func()
        print("Bye")
    return wrap

@decor2
@decor1
def greet():
    print("Welcome to python")

greet()

Output:

Bye
Hi
Welcome to python
Hi
Bye

See,how results are different when we changed order of decorators.This is because order in which they are applied matters.

That ‘s all for this topic.Hope, I have explained this topic well,If you have any doubt or suggestion,feel free to comment below.

Thank You.

Amarjeet

About Amarjeet

Amarjeet,BE in CS ,love to code in python and passionate about Machine Learning and Data Science. Expertsteaching.com is just a medium to share what I have learned till now with world.
Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *