Decorators in Python: Extending functionality of a function
Decorators are one of the most powerful concepts in python. A decorator is a function which takes another function or wraps a function to improve the behaviour of that function.
Table of contents
- Types of decorators in Python
- Prerequisites to understand decorators
- Decorators in Python
- Syntactic sugar to use decorators
- Decorator function with parameters
- Returning values from a decorated function
- Chaining decorators in python
- Using class as a Decorator
A decorator is a function which takes another function and extends its functionality without actually modifying it and finally returns it. In this tutorial, we will learn how to write decorators and why we should use it?
Types of decorators in Python
- Function Decorators
- Class Decorators
A decorator in Python is any callable Python object that is used to modify a function or a class.
Prerequisites to understand decorators
- In python everything (even classes) are objects. So, functions are also an object and we can assign different reference variables or names to it.
def greet(name):
print('hello ' + name)
greet('abhishek')
wish = greet
wish('abhishek')
Output -
hello abhishek
hello abhishek
- In python, functions can also be passed as an argument.
def greet(func, name):
func(name)
def sayHello(name):
print('Hello ' + name)
def sayBye(name):
print('Bye ' + name)
greet(sayHello, 'abhishek')
greet(sayBye, 'abhishek')
Such functions are known as higher-order functions.
Output -
Hello abhishek
Bye abhishek
- A function can also return another function.
def greet(name):
def sayHello():
print('Hello ' + name)
return sayHello
wish = greet('abhishek')
wish()
Output -
Hello abhishek
These are basics, which are required in order to understand decorators in python.
Decorators in Python
Basically, decorators are also a function but they accept functions as arguments and may perform some modifications to it.
def make_pretty(func):
def inner():
print('*************************')
func()
print('*************************')
return inner
def sayHello():
print('Hello world')
decorated_func = make_pretty(sayHello)
decorated_func()
Output -
*************************
Hello world
*************************
Here, you can observe that we are taking a function as an argument and defining a completely new function by calling that function and finally returning our new function which can be used now as a new version of our function.
Syntactic sugar to use decorators
We can use @
symbol with the name of decorator function to decorate our functions and it works in the same way.
Syntax -
@decorator_func_name
def func_name():
...
func_name() # calling
Using the syntactic sugar for decorators
def make_pretty(func):
def inner():
print('*************************')
func()
print('*************************')
return inner
@make_pretty
def sayHello():
print('Hello world')
sayHello()
Output -
*************************
Hello world
*************************
Decorator function with parameters
Example 1:
def repeat_twice(func):
def inner(name):
func(name)
func(name)
return inner
@repeat_twice
def greet(name):
print('Welcome ' + name)
greet('Abhishek')
Output -
Welcome Abhishek
Welcome Abhishek
Example 2:
def smart_greet(func):
def inner(name):
if name == 'Abhi':
print('Welcome ' + name)
else:
print('Welcome guest!')
return inner
@smart_greet
def greet(name):
print('Welcome ' + name)
greet('Abhi')
greet('Abhishek')
Output -
Welcome Abhi
Welcome guest!
Example 3:
def smart_divide(func):
def inner(a, b):
if b == 0:
print('Unable to divide!')
else:
func(a, b)
return inner
@smart_divide
def divide(a, b):
print(a // b)
divide(10, 0)
divide(10, 5)
Output -
Unable to divide!
2
Returning values from a decorated function
We can also return some values from a function which is being decorated.
def returning_func(func):
def inner(name):
return func(name)
return inner
@returning_func
def greet(name):
return 'Welcome ' + name
print(greet('Abhishek'))
Output -
Welcome Abhishek
Chaining decorators in python
We can even chain multiple decorators to a function. They will be executed sequentially in order.
Example 1:
def star(func):
def inner(name):
print('*' * 20)
func(name)
print('*' * 20)
return inner
def percent(func):
def inner(name):
print('%' * 20)
func(name)
print('%' * 20)
return inner
@star
@percent
def greet(name):
print('Welcomme ' + name)
greet('Abhi')
Output
********************
%%%%%%%%%%%%%%%%%%%%
Welcome Abhi
%%%%%%%%%%%%%%%%%%%%
********************
Example 2:
def star(func):
def inner(name):
print('*' * 20)
func(name)
print('*' * 20)
return inner
def percent(func):
def inner(name):
print('%' * 20)
func(name)
print('%' * 20)
return inner
@percent
@star
def greet(name):
print('Welcome ' + name)
greet('Abhi')
Output -
%%%%%%%%%%%%%%%%%%%%
********************
Welcome Abhi
********************
%%%%%%%%%%%%%%%%%%%%
Using class as a Decorator
We will rewrite following function decorator as class decorator.
def greet(func):
def inner():
print('*' * 20)
func()
print('*' * 20)
return inner
@greet
def greet_abhi():
print('Welcome Abhi')
greet_abhi()
Output -
********************
Welcome Abhi
********************
Following decorator works similar as above but uses class instead of function.
class greet:
def __init__(self, func):
self.func = func
def __call__(self):
print('*' * 20)
self.func()
print('*' * 20)
@greet
def greet_abhi():
print('Welcome Abhi')
greet_abhi()
Output -
********************
Welcome Abhi
********************