Decorators In Python

Decorators in python are are often regarded as convoluted. In this post we would try to make sense of them. Let’s start. Few other topics have been discussed to make understanding of Decorators easy.

HIGHER ORDER FUNCTIONS: In mathematics and computer science, a higher-order function is a function that does at least one of the following:

  • takes one or more functions as arguments (i.e. procedural parameters),
  • returns a function as its result.
def sayhello(name):              #regular functions
    return ('hello ' + name)
def sayhi(name):                 #regular functions
    return ('hi ' + name)
def greet(function_name):   #higher order-order function
    return function_name("James")

greet(sayhello)

greet(sayhi)

Output:

hello James
Hi James

As you can see there are two regular functions defined as sayhello( ), sayhi( ) and one higher order function greet( ). As we can see in higher-order function greet we are giving a function name as an argument and it is returning a function return. 

Inner Functions:

In python, we can define functions under functions. To illustrate this let take an example:


def outer():
    print("Printing from the outer() function")

    def first_inner():
        print("Printing from the first_inner() function")

    def second_inner():
        print("Printing from the second_inner() function")

    second_inner()
    first_inner()


outer()

Output:

Printing from the outer() function
Printing from the second_inner() function
Printing from the first_inner() function

As we can see from the above code inner functions have been defined inside outer function. Also, definition of inner functions does not effect how they are called inside the function. Obviously, you cannot call functions before defining them.

Let us try above concepts in below code:

def outer(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def say_hello():
    print("Hello!")

x = outer(say_hello)
x()

Output:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

We first give the x a function object pointing to the wrapper function( why? because the outer function returns wrapper function object). When we call the function x() then it being a wrapper function object calls wrapper function which prints out – “Something is happening before the function is called.” After that, as we’ve passed a say_hello function object to the outer function, hence it can be called now without giving any error. And finally, “Something is happening after the function is called.” is printed.

What is happening here can be explained below with the definition of Decorators. 

A decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

Now the line  “x = outer(say_hello) ” can be written in a more “decorative” way like this – 

def outer(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper
@outer                 ## that is our decorator ##
def say_hello():
    print("Hello!")

say_hello()

Output:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

What if the function say_hello() had one or more arguments? 

def outer(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@outer
def say_hello(name):
print("Hello! "+name)

say_hello("Shiva")

Output:

Traceback (most recent call last):
  File "/home/shivam/.PyCharmCE2018.2/config/scratches/scratch.py", line 1300, in <module>
    say_hello("Shiva")
TypeError: wrapper() takes 0 positional arguments but 1 was given

Process finished with exit code 1

To make it work we will use: 

  1. *args (Non Keyword Arguments)
  2. **kwargs (Keyword Arguments)

Since func function under wrapper function expects arguments we have to provide it with them in this way. Look at the correct code:

def outer(func):
    def wrapper(*args,**kwargs):
        print("Something is happening before the function is called.")
        func(*args,**kwargs)
        print("Something is happening after the function is called.")
    return wrapper
@outer
def say_hello(name):
    print("Hello! "+name)


say_hello("Shiva")

Output:

Something is happening before the function is called.
Hello! Shiva
Something is happening after the function is called.

Leave a Reply