As the name suggests, decorators in python are special functions which adds additional functionality to an existing function or code.
For example, you had a white car with basic wheel setup and a mechanic changes the color of your car to red and fits alloy wheels to it then the mechanic decorated your car, similarly a decorator in python is used to decorate(or add functionality or feature) to your existing code.
Before we learn the concept of decorators we must know a few things about functions.
In Python everything is an object and can be referenced using a variable name. Yes, even functions are objects with attributes.
We can have multiple variables reference the same function object(definition), for example:
def one(msg): print(msg) # calling the function one("Hello!") # having a new variable reference it two = one # calling the new variable two("Hello!")
Similarly, we can pass a function as an argument too. For example:
# some function def first(msg): print(msg) # second function def second(func, msg): func(msg) # calling the second function with first as argument second(first, "Hello!")
While in the example above the function
second took the function
first as an argument and used it, a function can also return a function.
When there is nested functions(function inside a function) and the outer function returns the inner function it is known as Closure in python, about which we learned in our last tutorial.
Decorators in python is sort of an extension to the concept closures in python.
A decorator gives a function a new behavior without changing the function itself. A decorator is used to add functionality to a function or a class. In other words, python decorators wrap another function and extends the behavior of the wrapped function, without permanently modifying it.
Now, Let's understand the decorators using an example,
# a decorator function def myDecor(func): # inner function like in closures def wrapper(): print("Modified function") func() return wrapper def myfunc(): print('Hello!!') # Calling myfunc() myfunc() # decorating the myfunc function decorated_myfunc = myDecor(myfunc) # calling the decorated version decorated_myfunc()
Modified function Hello!!
In the code example above, we have followed the closure approach but instead of some variable, we are passing a function as argument, hence executing the function with some more code statements.
We passed the function
myfunc as argument to the function
myDecor to get the decorated version of the
Now rather than passing the function as argument to the decorator function, python provides us with a simple way of doing this, using the
# using the decorator function @myDecor def myfunc(): print('Hello!!') # Calling myfunc() myfunc()
Modified function Hello!!
In the code example above,
@myDecor is used to attach the
myDecor() decorator to any function you want. So when we will call
myfunc(), instead of execution of the actual body of
myfunc() function, it will be passed as an argument to
myDecor() and the modified version of
myfunc() is returned which will be executed.
So, basically @<Decorator_name> is used to attach any decorator with name Decorator_name to any function in python programming language.
Till now we have seen the use of decorators to modify function that hasn't used any argument. Now, let's see how to use argument with a function which is to be decorated.
For this, we are going to use
**kwargs as the arguments in the inner function of the decorator.
*args in function definition is used to pass a variable number of arguments to any function. It is used to pass a non-keyworded, variable-length argument list.
**kwargs in function definitions is used to pass a keyworded, variable-length argument list. We use the name kwargs with the double star. The reason is that the double star allows us to pass through keyword arguments (and any number of them).
def myDecor(func): def wrapper(*args, **kwargs): print('Modified function') func(*args, **kwargs) return wrapper @myDecor def myfunc(msg): print(msg) # calling myfunc() myfunc('Hey')
Modified function Hey
In the example, the
myfunc() function is taking an argument
msg which is a message that will be printed. The call will result in the decorating of function by myDecor decorator and argument passed to it will be as a result passed to the args of
wrapper() function which will again pass those arguments while calling
myfunc() function. And finally, the message passed will be printed after the statement 'Modified function'.
We can use more than one decorator to decorate a function by chaining them. Let's understand it with an example,
# first decorator def star(f): def wrapped(): return '**' + f() + '**' return wrapped # second decorator def plus(f): def wrapped(): return '++' + f() + '++' return wrapped @star @plus def hello(): return 'hello' print(hello())
In the above example,
plus decorators are defined that can add the ** and ++ to our message. These both are attached to
hello() function and hence they simultaneously modified the function, decorating the output message.
Decorators are very often used for adding the timing and logging functionalities to the normal functions in a python program. Let's see one example where we will add the timing functionalities to two functions:
import time def timing(f): def wrapper(*args, **kwargs): start = time.time() result = f(*args,**kwargs) end = time.time() print(f.__name__ +" took " + str((end-start)*1000) + " mil sec") return result return wrapper @timing def calcSquare(numbers): result =  for number in numbers: result.append(number*number) return result @timing def calcCube(numbers): result =  for number in numbers: result.append(number*number*number) return result # main method if __name__ == '__main__': array = range(1,100000) sq = calcSquare(array) cube = calcCube(array)
calcSquare took 60.42599678039551 mil sec calcCube took 52.678823471069336 mil sec
In the above example, we have created two functions
calcSquare which are used to calculate square and cube of a list of numbers respectively. Now, we want to calculate the time it takes to execute both the functions, for that we have defined a decorator
timing which will calculate the time it took in executing both the functions.
Here we have used the time module and the time before starting a function to start variable and the time after a function ends to end variable.
f.__name__ gives the name of the current function that is being decorated. The code
range(1,100000) returned a list of numbers from 1 to 100000.
So, by using decorators, we avoided using the same code in both the functions separately (to get the time of execution). This helped us in maintaining a clean code as well as reduced the work overhead.