Open In App

Useful cases to illustrate Decorators in python

Improve
Improve
Like Article
Like
Save
Share
Report

A decorator is a special kind of function that either takes a function and returns a function or takes a class and returns a class. Well, it can be any callable (i.e functions, classes, methods as they can be called) and it can return anything, it can also take a method. This is also called metaprogramming, as a part of the program tries to modify another part of the program at compile time.
Let’s dive into python decorators and find out what they can do. This will not be covering the basics or decorators with parameters, but some useful examples to illustrate the case.

Refer the below article to get the basics of Python decorators

Basically, a decorator takes in a callable, any object which implements the special method __call()__ is termed as callable, adds some functionality and returns a callable.

Example 1:




# Python program to demonstrate
# decorators
  
  
# Creating a decorator
def decorated_func(func):
    def inner():
        print("This is decorated function")
        func()
    return inner()
  
  
def ordinary_func ():
    print("This is ordinary function")
  
decorated = decorated_func(ordinary_func)
decorated


Output:

This is decorated function
This is ordinary function

In the example shown above, decorated_func() is a decorator. In short, a decorator acts as a wrapper that wraps an object (does not alter the original object) and adds an new functionality to original object. This is a common construct, so Python has a syntax feature (called Decorator) to simplify this. For example,

This:

@decorated_func
def ordinary_func():
     print("This is ordinary function")

is Equivalent to:

def ordinary_func():
    print("This is ordinary function")
decorated = decorated_func(ordinary_func)

A simple example would be:

Example 2:
Input:




def mul_decorator(func):
    def wrapper(*args, **kwargs):
        print('function', func.__name__, 'called with args - ', /
              args, 'and kwargs - ', kwargs)
        result = func(*args, **kwargs)
        print('function', func.__name__, 'returns', result)
        return result
    return wrapper
  
  
@mul_decorator
def mul(a, b):
    return a * b
mul(3, 3)
mul(3, b = 6)


Output:

function mul called with args -  (3, 3) and kwargs -  {}
function mul returns 9
function mul called with args -  (3,) and kwargs -  {'b': 6}
function mul returns 18

You can also use the built-ins as decorators

Example 3:




# func will be func = type(func) -> <class 'function'>
@type
def func(): 
    return 42
  
print(func)
  
# print doesn't return anything, so func == None
@print
def func2(): 
    return 42
  
# Prints None
print(func2)


Output:

<class 'function'>
<function func2 at 0x7f135f067f28>
None

You can replace decorated object with something else

Example 4:




# Creating a decorator
class function_1:
    def __init__(self, func):
        self.func = func
        self.stats = []
  
    def __call__(self, *args, **kwargs):
        try:
            result = self.func(*args, **kwargs)
        except Exception as e:
            self.stats.append((args, kwargs, e))
            raise e
        else:
            self.stats.append((args, kwargs, result))
            return result
  
    @classmethod
    def function_2(cls, func):
        return cls(func)
  
  
@function_1.function_2
def func(x, y):
    return x / y
  
print(func(6, 2))
  
print(func(x = 6, y = 4))
  
func(5, 0)
print(func.stats)
print(func)


Output:

3.0
1.5
Traceback (most recent call last):
  File "/home/1ba974e44c61e303979b3ee120b6b066.py", line 29, in 
    func(5, 0)
  File "/home/1ba974e44c61e303979b3ee120b6b066.py", line 11, in __call__
    raise e
  File "/home/1ba974e44c61e303979b3ee120b6b066.py", line 8, in __call__
    result = self.func(*args, **kwargs)
  File "/home/1ba974e44c61e303979b3ee120b6b066.py", line 23, in func
    return x / y
ZeroDivisionError: division by zero

Notice how the original "func" was replaced by an instance of "function_1", which can be used in the same way as the original function.
You can create relation with other objects in system

Example 5:




def dict_from_func(func):
    return {func.__name__: func}
  
  
activity = {}
  
@activity.update
@dict_from_func
def mul(a, b):
    return a * b
  
  
@activity.update
@dict_from_func
def add(a, b):
    return a + b
  
  
print(mul)
print(activity)
print(activity['mul'](2, 5))


Output:

None
{'mul': <function mul at 0x7f0d2209fe18>, 
 'add': <function add at 0x7f0d220a2158>}
10

Here, in the example 5, we have used dict.update method as a decorator, even if it is not intended for this. This, is possible because dict_from_func returns a dict, and dict.update takes a dict as an argument.

Actually, This:

@activity.update
@dict_from_func
def mul(a, b):
    return a * b

Equals this –

def mul(a, b):
    return a * b
mul = activity.update(dict_from_func(mul))

Conclusion

Decorators is an interesting and amazing feature and can be used for variety of purposes. It’s not just “function or class that takes function or a class and returns a function or a class”.



Last Updated : 11 Dec, 2019
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads