Python Decorators
Video Lecture
Decorators provide a simple syntax for calling higher-order functions.
By the definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.
Prerequisites to learn Decorators :
To understand and implement decorators, you must know the following concepts about the functions in details
- Functions are first class objects. (higher order function)
- In Python, everything is object (functions too) and can be referenced using a variable name.
- The concepts of inner functions.
- Decorators in python is sort of an extension to the concept closures in python. So, you must know the concepts of closures.
A quick recap with the code example to understand the above said concepts.
Function referenced using a variable name.
def myfun(var): print(var) myfun("Hello") ref = myfun ' ref ' is reference of function ref("Hello") # calling the function using referenceOUTPUT
Hello Hello
Functions can be passed as arguments to another function.
Functions that take other functions as arguments are called higher order functions.
def square(num): return(num * num) def cube(num): return(num*num*num) def calculate(fun, n): res = fun(n) print(res) calculate(square, 5) #passing square as argument calculate(cube, 5) #passing cube as argumentOUTPUT
25 125
Furthermore, a function can return another function.
def outer_func(): def inner_func(): print("Hello") return inner_func fun = outer_func() fun()OUTPUT
Hello
Here, inner_func() is a nested function which is defined and returned each time when we call outer_func().
Another concept which is very important that we must know closures in Python.
Decorators in Python
Python decorators are convenient ways to make changes to the functionality of code without making changes to the code.
Decorator allows programmers to modify the behavior of function or class.
Decorators allow us to wrap another function in order to extend the behavior of wrapped function, without permanently modifying it.
In simple words, a decorator takes in a function, adds some functionality and returns it.
# a decorator function def myDecorator(func): # inner function like in closures def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper def myfunc(): print('Hello') # decorating the myfunc function decorated_myfunc = myDecorator(myfunc) # calling the decorated version decorated_myfunc()OUTPUT
Something is happening before the function is called. Hello Something is happening after the function is called.
Let's understand what is going in above code.
Decoration happens in the following line of code:
decorated_myfunc = myDecorator(myfunc)
In the above code, the var name decorated_myfunc is pointing to the wrapper() inner function, And we are returning wrapper as a function whenever we call myDecorator(func).
However, wrapper() has a reference to the original myfunc() as func, and calls that function between the two calls to print().
Simply :decorators wrap a function, modifying its behavior.
Python has a simple syntax to decorate a function. 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,
@myDecorator def myfunc(): print('Hello')
is equivalent to
def myfunc(): print('Hello') # decorating the myfunc function decorated_myfunc = myDecorator(myfunc)
This is just a syntactic sugar to implement decorators.
Code to implement decorator using @
sysmbol.
# a decorator function def myDecorator(func): # inner function like in closures def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper @myDecorator def myfunc(): print('Hello') myfunc()OUTPUT
Something is happening before the function is called. Hello Something is happening after the function is called.
Decorating Functions With Arguments
Suppose that we have a function that accepts some arguments. Can we still decorate it by the same decorator? Let's try
def myDecorator(func): def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper @myDecorator def myfunc(name): print(f'Hello {name}') myfunc("World")OUTPUT
Traceback (most recent call last): TypeError: wrapper() takes 0 positional arguments but 1 was given
Unfortunately, above code raises an error:
The The problem is that the inner function wrapper() does not take any arguments, but name="World" was passed to it. We could fix this by letting wrapper() accept one argument.
def myDecorator(func): def wrapper(arg): print("Something is happening before the function is called.") func(arg) print("Something is happening after the function is called.") return wrapper @myDecorator def myfunc(name): print(f'Hello {name}') myfunc("World")
From the above examples, we can observe that the parameters of the inner wrapper() function inside the decorator is the same as the parameters of functions it decorates. On the basis of above observation, we can make general decorators that work with arbitrary number of parameters.
The solution is to use *args
and **kwargs
in the inner wrapper function. Then it will accept an arbitrary
number of positional and keyword arguments.
CODE:
def myDecorator(func): def wrapper(*args, **kwargs): print("I am decorating all functions") func(*args, **kwargs) return wrapper @myDecorator def myfunc(): print("Function without argument") @myDecorator def myfunc1(name): print(f'function with argument') myfunc() print("###########") myfunc1("Hello")OUTPUT
I am decorating all functions Function without argument ########### I am decorating all functions function with argument
As we can see that both the function myfunc() and myfunc(name) are decorated using the same decorator.
Chaining The Decorators in Python
A function can be decorated multiple times with different or same decorators. We can use more than one decorator to decorate a function by chaining them.
EXAMPLE :
def myDecorator1(func): def wrapper(*args, **kwargs): print("I am first decorator") func(*args, **kwargs) print("First Decorator ends") return wrapper def myDecorator2(func): def wrapper(*args, **kwargs): print("I am second decorator") func(*args, **kwargs) print("Second Decorator Ends") return wrapper @myDecorator1 @myDecorator2 def myfunc(): print("Actual statements from function") myfunc()OUTPUT
I am first decorator I am second decorator Actual statements from function Second Decorator Ends First Decorator ends
The above syntax of,
@myDecorator1 @myDecorator2 def myfunc(): print("Actual statements from function")
is equivalent to
def myfunc(): print("Actual statements from function") fun = myDecorator1(myDecorator2(myfunc))
The order in which we chain decorators matter. If we change the order then output will be change.
Real World Example
Sometimes we want to find the execution time of a function. One way is to calculate the execution time is, write the logic of time calculation within the function and second way is to use the decorator. The question is which one is best suited? The answer is decorators. Why decorator? Answer is, we all know that the function's functionality is to perform the desired task for which that is created, and we want to add extra functionality to calculate the execution time, and this can be done by decorators without modifying the actual code of function. And one more thing, this decorator can be use with any functions.
import time def time_cal(fun): def wrapper(*args,**kwargs): start_time = time.time() result = fun(*args,**kwargs) end_time = time.time() print (fun.__name__," execution time taken =",(end_time-start_time)*1000," milli sec") return result return wrapper @time_cal def square(num): result = [] for x in num: result.append(x * x) return result @time_cal def cube(num): result = [] for x in num: result.append(x * x * x) return result y = range(1, 100000) square(y) cube(y)OUTPUT
square execution time taken = 15.58685302734375 milli sec cube execution time taken = 31.24380111694336 milli sec
Next chapter is Anonymous Function
Training For College Campus
We offers college campus training for all streams like CS, IT, ECE, Mechanical, Civil etc. on different technologies
like
C, C++, Data Structure, Core Java, Advance Java, Struts Framework, Hibernate, Python, Android, Big-Data, Ebedded & Robotics etc.
Please mail your requirement at info@prowessapps.in
Projects For Students
Students can contact us for their projects on different technologies Core Java, Advance Java, Android etc.
Students can mail requirement at info@prowessapps.in