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