Useful cases to illustrate Decorators in python

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:

filter_none

edit
close

play_arrow

link
brightness_4
code

# 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

chevron_right


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:

filter_none

edit
close

play_arrow

link
brightness_4
code

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)

chevron_right


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:



filter_none

edit
close

play_arrow

link
brightness_4
code

# 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)

chevron_right


Output:

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

You can replace decorated object with something else

Example 4:

filter_none

edit
close

play_arrow

link
brightness_4
code

# 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)

chevron_right


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:

filter_none

edit
close

play_arrow

link
brightness_4
code

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))

chevron_right


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”.




My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.


Article Tags :

1


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.