fork-git

Decorator is a type of function that accepts another function as an argument. By doing so it enables us to add or modify the behavior of the function that is passed as an argument, without having to modify the function itself.

Lets assume we have a function to display the balance amount in a wallet. Lets suppose, we want to be able round of the balance value. Though it would be convenient for us to modify the function directly, to round of value, it would not be a good practice. We might end up breaking the behavior of other functions that depend on this, resulting in a need to make a lot of code changes. In order to make life easier, we can define a decorator function (the behavior modifier) and call it wherever the original function needs to behave differently.

Decorators without arguments

In the following example, function ‘show_amount’ displays the amount as is, when called. We will create a decorator function to display the rounded off amount instead. Here decorator function ’round_off’ accepts ‘show_amount’ as an argument. Note that, This is made possible by adding @round_off to the ‘show_amount’ function. This is a python short hand equivalent of round_off(show_amount).

’round_off’ returns a nested function ‘inner’, which is the actual behavior modifier, wherein ‘fun’ is nothing but the instance. Here is where the round off happens.

#!/usr/bin/env python

# Decorator method, modifies behavior of an existing method, when called.
def round_off(method):
    def inner(fun):
        fun.amount = round(fun.amount)
        method(fun)
    return inner

class Wallet:
    def __init__(self):
        self.amount = 81.9987

    def show_amount(self):
        print self.amount

# Child class inheriting method 'show_amount' without Decorator 'round_off'
class Wallet_Old(Wallet, object):
    def show_amount(self):
        super(Wallet_Old, self).show_amount()

# Child class inheriting method 'show_amount' with Decorator 'round_off'
class Wallet_New(Wallet, object):
    @round_off
    def show_amount(self):
        super(Wallet_New, self).show_amount()

      

W = Wallet_Old()
W.show_amount() 

W = Wallet_New()
W.show_amount()

In this code snippet, for demonstration, we’ve called the method ‘show_amount’ with & with out decorator. Note that, when decorator is called with ‘@round_off’, the value is rounded off.

Output:

81.9987
82.0

Decorators with arguments

Lets suppose we want we also want to be able to accept the round off decimal as argument, it can be made possible by wrapping the decorator inside another wrapper function, which can accept arguments to be used within the inner method.

#!/usr/bin/env python

# Decorator method, modifies behavior of an existing method, when called.
# Here, since we wanted to pass an attribute to decorator, 
# we wrap the decorator method 'round_off_decor' inside a wrapper function 'round_off'.

def round_off(round_off_by):
    def round_off_decor(method):
        def inner(fun):
            fun.amount = round(fun.amount, round_off_by)
            method(fun)
        return inner
    return round_off_decor

class Wallet:
    def __init__(self):
        self.amount = 81.5687

    def show_amount(self):
        print self.amount

# Child class inheriting method 'show_amount' without Decorator 'round_off'
class Wallet_Old(Wallet, object):
    def show_amount(self):
        super(Wallet_Old, self).show_amount()

# Child class inheriting method 'show_amount' with Decorator 'round_off'
class Wallet_New(Wallet, object):
    @round_off(round_off_by=2)
    def show_amount(self):
        super(Wallet_New, self).show_amount()

      

W = Wallet_Old()
W.show_amount() 

W = Wallet_New()
W.show_amount()

Note that, ‘@round_off’ accepts the argument ’round_off_by’ which is passed to the wrapper function of the decorator.

Output:

81.5687
81.57