Python Decorators
Python Decorators
A decorator takes in a function, adds some functionality and returns it. In this article, you will learn how you can create a decorator and why
you should use it.
Python has an interesting feature called decorators to add functionality to an existing code.
This is also called metaprogramming as a part of the program tries to modify another part of the program at compile time.
We must be comfortable with the fact that, everything in Python (Yes! Even classes), are objects. Names that we define are simply identifiers bound to
these objects. Functions are no exceptions, they are objects too (with attributes). Various different names can be bound to the same function object.
Here is an example.
def first(msg):
print(msg)
first("Hello")
second = first
second("Hello")
When you run the code, both functions first and second gives same output. Here, the names first and second refer to the same function object.
If you have used functions like map, filter and reduce in Python, then you already know about this.
Such function that take other functions as arguments are also called higher order functions. Here is an example of such a function.
def inc(x):
return x + 1
def dec(x):
return x - 1
result = func(x)
return result
>>> operate(inc,3)
4
>>> operate(dec,3)
2
def is_called():
def is_returned():
print("Hello")
return is_returned
new = is_called()
#Outputs "Hello"
new()
Here, is_returned() is a nested function which is defined and returned, each time we call is_called().
Basically, a decorator takes in a function, adds some functionality and returns it.
def make_pretty(func):
def inner():
func()
return inner
def ordinary():
print("I am ordinary")
>>> ordinary()
I am ordinary
pretty = make_pretty(ordinary)
The function ordinary() got decorated and the returned function was given the name pretty.
We can see that the decorator function added some new functionality to the original function. This is similar to packing a gift. The decorator acts as a
wrapper. The nature of the object that got decorated (actual gift inside) does not alter. But now, it looks pretty (since it got decorated).
ordinary = make_pretty(ordinary).
This is a common construct and for this reason, Python has a syntax to simplify this.
We can use the @ symbol along with the name of the decorator function and place it above the definition of the function to be decorated. For example,
@make_pretty
def ordinary():
print("I am ordinary")
is equivalent to
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
This function has two parameters, a and b. We know, it will give error if we pass in b as 0.
>>> divide(2,5)
0.4
>>> divide(2,0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
Now let's make a decorator to check for this case that will cause the error.
def smart_divide(func):
def inner(a,b):
if b == 0:
return
return func(a,b)
return inner
@smart_divide
def divide(a,b):
return a/b
This new implementation will return None if the error condition arises.
>>> divide(2,5)
I am going to divide 2 and 5
0.4
>>> divide(2,0)
I am going to divide 2 and 0
Whoops! cannot divide