Python has a feature called decorators to add functionality to an existing code
It is similar to Java Annotations
A decorator takes in a function, adds some functionality and returns it
This is also called metaprogramming as a part of the program tries to modify another part of the program at compile time
In Python, Functions and classes are objects
Names that are defined are simply identifiers bound to these objects
Different names can be bound to the same function object
>>> def first():
... return 1
...
>>> second = first
>>> first
<function first at 0x7f0bf1783b70>
>>> second
<function first at 0x7f0bf1783b70>
Here, the names first
and second
refer to the same function object
Functions can be passed as arguments to another function
Functions like map
, filter
and reduce
utilize this
The function can be invoked as
>>> def operate(fn, value):
... return fn(value)
...
>>> def inc(value):
... return value + 1
...
>>> def dec(value):
... return value - 1
...
>>> operate(inc, 4)
5
>>> operate(dec, 4)
3
Furthermore, a function can return another function
Functions and methods are called callable as they can be called
In python, any object which implements the method __call__()
is termed callable
So, in a basic sense, a decorator is a callable that returns a callable
Basically, a decorator takes in a function, adds some functionality and returns it
Consider the following:
def decorate(fn):
def wrapper():
print("━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫")
fn()
print("━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫")
print("wrapper is executed")
return wrapper
def print_str():
print("print_str() function is executed")
print_str()
new_fn = decorate(print_str)
new_fn()
new_fn()
Output
print_str() function is executed
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫
print_str() function is executed
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫
wrapper is executed
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫
print_str() function is executed
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫
wrapper is executed
In the example shown above, decorate()
is a decorator
In the assignment step new_fn = decorate(print_str)
, the function print_str()
got decorated (i.e. wrapped inside a new function) and the returned function was given the name new_fn
The decorator function decorate()
added some new functionality to the original function
To simplify the syntax (in terms of re-usability), the @
symbol can be used along with the name of decorator function and placed above the definition of the function to be decorated
def decorate(fn):
def wrapper():
print("━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫")
fn()
print("━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫")
print("wrapper is executed")
return wrapper
@decorate
def print_str():
print("print_str() function is executed")
print_str()
Output
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫
print_str() function is executed
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫
wrapper is executed
Conside print_str()
function shown above, is changed to a function which takes 2 arguments and prints them
def print_str(value1, value2):
print(value1, value2)
The decorator wrapper wrapper()
function has to be modified to pass any received parameters to the decorated function
def decorate(fn):
def wrapper(*args, **kwargs):
print("━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫")
fn(*args, **kwargs)
print("━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫")
print("wrapper is executed")
return wrapper
@decorate
def print_str(value1, value2):
print(value1 + " and " + value2)
print_str("A string", "another string input to decorated print_str()")
Output
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫
A string and another string input to decorated print_str()
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┫
wrapper is executed
This way, general decorators can be made that work with any number of parameter
Following decorator can be used to handle exceptions and print exception and prevent program from exiting
def handle_exception(fn):
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception as e:
print("Exception:", e)
return None
return wrapper
@handle_exception
def divide(value1, value2):
return float(value1) / float(value2)
divide(10, 32)
divide("123", 0)
As shown, it can be used with a divide()
function to handle exceptions during division and conversion to float
Multiple decorators can be chained in Python, allowing a function to be decorated multiple times with different (or same) decorators
The decorators are simply placed above the desired function
def decorate(fn):
def wrapper():
print("━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┓")
fn()
print("━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┛")
return wrapper
def decorate_star(fn):
def wrapper():
print("***************************************************************************************")
fn()
print("***************************************************************************************")
return wrapper
@decorate
@decorate_star
def print_str():
print("print_str() function is executed")
print_str()
Output
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┓
***************************************************************************************
print_str() function is executed
***************************************************************************************
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┛
Here, at first the print_str() function is decorated by decorate_star() function and the wrapper function which is returned by decorate_star() is then passed to decorate() function which wraps it and returns the wrapper
The above syntax of
@decorate
@decorate_star
def print_str():
print("print_str() function is executed")
is equivalent to
def print_str():
print("print_str() function is executed")
print_str = decorate(decorate_star(print_str))
The order in which the decorators appear, matter
If the order for above example is reversed as
@decorate_star
@decorate
def print_str():
print("print_str() function is executed")
then its displayed as :
***************************************************************************************
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┓
print_str() function is executed
━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━━┅┅┅┅┅━━━━━┛
***************************************************************************************