How to implement custom Decorator in Python

How to implement custom Decorator in Python

Introduction to Decorator design-pattern

A decorator pattern is a well-known structural design pattern used to add new behavior to an existing object dynamically without altering already existing behavior. Decorator works by placing the object inside wrapper objects to add the functionality of the wrapper objects to the wrapped object.

By using decorator pattern, you can achieve the Signle-Responsibilty Principle by dividing the responsibilities among different functions. It also helps to maintain Open-Close Principle as you can add any new functionality to a new object without modifying the existing object's functionality.

Simplifying decorator pattern in python with examples

def func_a(arg):
    print("I am from A with argumnet "+ arg)

def decorator(func, arg):
    func(arg)

main(func_a, "C")
I am from A with argumnet C

In the above example, the decorator function is working as a higher-order function which is taking a function as an argument and calling it. Right now it's not doing any other task. Let's assign a task to the decorator function.

def func_a(arg):
    print("I am from A with argumnet "+ arg)

def decorator(func, arg):
    print("I am in decorator with argument " + arg)
    func(arg)
func_a("D")
I am from A with argumnet D

In the above example, I'm calling the function and it's printing the line mentioned above as expected.

decorator(func_a, "C")
I am in decorator with argumnet C
I am from A with argumnet C

Now I've added additional behavior to the existing func_a by adding it inside a decorator without changing its existing behavior.

Understanding Decorators in python

Now Let's come to Python decorators

def decorator(func):
    def helper(arg):
        print("I am in decorator with argumnet " + arg)
        func(arg)
    return helper

def func_a(arg):
    print("I am from A with argumnet "+ arg)
decorated_func = decorator(func_a)
decorated_func("C")
I am in decorator with argumnet C
I am from A with argumnet C

The '@' syntax of python Decorators

The above code in python can also be written like

def decorator(func):
    def helper(arg):
        print("I am in decorator with argumnet " + arg)
        func(arg)
    return helper

@decorator
def func_a(arg):
    print("I am from A with argumnet "+ arg)
fun_a("C")
I am in decorator with argumnet C
I am from A with argumnet C

The "@" symbol in python is shorthand for writing decorators.

Let's go with a more realistic example where we can use a decorator effectively

def hanle_index_exception(func):
    def helper(arr, pos):
        if pos >= len(arr):
            return "position is greater that list length."
        return func(arr, pos)
    return helper

@hanle_index_exception
def value_at_position(arr , pos):
    return arr[pos]

arr = [1, 3, 5, 7]
val_1 = value_at_position(arr, 2)
print(val_1)
val_2 = value_at_position(arr, 4)
print(val_2)
5
position is greater that list length.

In the above example, we're using a decorator to handle index out-of-bound exceptions.

Chaining of decorators

Multiple decorators can also be chained in python means decorating a function multiple times.

Here is a simple example:

def remove_whitespaces(func):
    def hepler(name):
        name = name.strip()
        func(name)
    return hepler

def format(func):
    def hepler(name):
        name = name.capitalize()
        func(name)
    return hepler

@remove_whitespaces
@format
def print_name(name):
    print(name)

print_name('    akash  ')
Akash

In the above example, remove_whitespaces decorator removes the whitespaces first. Then format decorator made the first letter capitalized. You should maintain the order correctly when adding more than one decorator as the topmost one will be applied first.

I hope now we have a better understanding of how to use decorator in python, you can easily use some decorator in your next project.

Happy Learning !!