Thursday, October 7, 2021

Decorators in Python

 Decorators to add new functionality to the existing code. Basically a decorator takes in a function, add functionality and return it.

Before understanding Decorators, we need to get some knowledge on Nested functions, Non-local variables and closures.

Nested Functions: A function is defined inside a function is called Nested function.

Program: 
def outer_function():
    # This is a outer function
    def inner_function():
        # This is a inner function
        print("This is Inner Function")
    inner_function()
    # inner function called

outer_function()
# outer function called

Output:
This is a Inner Function

Non-local Variables: Nested functions (Inner function) can access variables of enclosing scope (Outer Function)

Program:
def outer_function(message):
    # This is a outer function
    def inner_function():
        # Accessing outer function variable
        print(message)
        # This is a inner function
        print("This is Inner Function")
    inner_function()
    # inner function called

outer_function("Hello,")
# outer function called

Output:
Hello,
This is Inner Function

Closures: Closure is a function object that remembers values (inner function) in the enclosing scopes (outer function)

Program:
  1. def calculate_squares(exponent):
  2.     def squares(base):
  3.         return pow(base, exponent)
  4.     return squares # In closures we need to return the function without parenthesis 

  5. square = calculate_squares(2)
  6. print(square(2))
  7. print(square(3))

Output:
4
9

Will try to understand the above program. We called calculate_squares(2) in line6 and it returned a function squares in line4. The returned function squares bound to the name square in line6. In simple terms squares function is stored in square variable with calculate_squares function value 2 (non-local variable).

In line7 and 8, we are calling square(2) and square(3) that means square variable having inner function squares() with values of outer function calculate_squares. Here the value is exponent = 2 which is non local variable.

Decorators: As mentioned above decorator takes in a function, add functionality and return it.

Program:
  1. def decorator_func(func):
  2.     def wrapper_func():
  3.         print("Hi")
  4.         func()
  5.     return wrapper_func

  6. def greeting_func():
  7.     print("How are you")

  8. hi = decorator_func(greeting_func)
  9. hi()
Output:
Hi
How are you

Will try to understand above program, greeting_func() function only prints "How are you", we feel the greeting is incomplete, we want to add "Hi" before "How are you", we need add some functionality to greeting_func() function to complete our requirement.

As mentioned in the definition decorator takes in a function and adds functionality and return it. So in line10 decorator_func() takes greeting_func() as input. So before calling the greeting_func() we added a print statement in line3. So we got the above ouput. Here we didn't touch greeting_func() but we added some functionality to it.

The above program is equivalent to the below program.

Program:
  1. def decorator_func(func):
  2.     def wrapper_func():
  3.         print("Hi")
  4.         func()
  5.     return wrapper_func
  6. @decorator_func  # This line is equivalent to the lines 10 and 11 in the above program
  7. def greeting_func():
  8.     print("How are you")
  9. greeting_func()

Sample Program: 1

def decorator_func(func):

    def wrapper_func(x,y):
        print("division between", x,"and", y)
        if y == 0:
            print("Divisible by zero is not possible")
            return
        return x/y
    return wrapper_func

@decorator_func
def division(x,y):
    return x/y

print(division(10,2))
print(division(10,5))
print(division(10,0))

output:
division between 10 and 2
5.0
division between 10 and 5
2.0
division between 10 and 0
Divisible by zero is not possible
None