Decorators in Python are bit confusing concept to understand. It is not a standard feature generally explained in many of the Beginners’ books on Python. But, understanding the basic idea of decorators will help us in making a very good use of it. As the rule always goes, there are certain situations where decorators are highly useful but not always.
Decorators in a way of transforming functions and methods at the point where the code is declared. The point of transformation is placed just before the method’s own declaration.
To give a more clear idea, we generally put some code within a function, make use of other functions by calling them, performing some calculations or processing within the function’s body, and finally return some result or print some output.
Think of a situation where the attributes of the function get modified when the function is actually executed. That is, we start the function feeding it with a specific argument/parameter over which the function code operates. But, instead of directly passing over the input arguments to the function code, we rather try to do something with these arguments, modify them, process them and then pass it to the function as though they are untouched. Thus we make the function feel as though it is receiving the input as it is, but we are actually modifying them. The important point to note is these things happen at function execution time, just before the original function code executes.
The original proposal of Decorators as an enhancement and the discussion thereby are documented in PEP (Python Enhancement Proposal) 318. There are also few examples given to show how decorators can be implemented in general. But, for a newbie these examples are not easy yo grasp.
I myself have to thank Siddhartha and Rajeev J Sebastian for helping me with additional examples and explanations respectively, for me to understand the concept and deliver my talk during FOSS.NITC 2007.
Let me start with Siddhartha’s simple example - Decorating a custom method with a decorator which prints text around the function call.
Example 1:
def decorate(fn):
def _decorate():
print "Before Calling"
fn()
print "After Calling"
return _decorate
@decorate
def mymethod():
print "Inside Function"
mymethod()
Output:
Before Calling
Inside Function
After Calling
Explanation:
A decorator is another object which expects to be fed with a function object. What the decorator does ? When the function is called, instead of allowing it to execute normally, if fetches the function object. It can modify the attributes of the function object or add new attributes. Instead it can just print some thing else and call the original function and execute it, in the middle of its own code as shown above. Just it prints the first text, calls the function which prints its own text, then it prints the text following the function call.
All of these happens at run time, when the function is actually called. Otherwise, its just a definition of another method. Thus, decorators jumps into action only when the function is actually called somewhere.
Another example might help us in understanding decorators much better. This time we will try to do something with the arguments passed to the function itself using the decorator and enjoy the fun.
Example 2:
def params(fn):
def _inner(x):
print "You called me with %d, but I rather called with %d" %(x,x+1)
fn(x+1)
return _inner
@params
def myfunc(x):
print "I got", x
myfunc(5)
Output:
You called me with 5, but I rather called with 6
I got 6
Explanation:
The decorator params() is used to dynamically add 1 to the input argument and call the function myfunc() with the changed argument. Thus when we call the function with a value 5 for x, the decorators calls the function with a value 6. What the function expects is a input argument x and it prints it. But what to be noted is, the decorator modified the parameter passed to the function and executed it with the modified parameter.
Ok, what else we can do ? We can pass a parameter to the decorator itself and use it to perform some operation on the function over which the decorator is applied. This example illustrates such an application of decorators.
Example 3:
def adapttoinput(str):
def _multdecor(fn):
def _inner(a,b):
fn(a*b)
return _inner
def _adddecor(fn):
def _inner(a,b):
fn(a+b)
return _inner
if str == "*":
return _multdecor
else:
return _adddecor
@adapttoinput("*")
def mymethod(a):
print "The output is", a
mymethod(2,3)
Output:
The output is 6
Explanation:
Though the example looks ordinary, one interesting point needs to be noticed. The original function mymethod() accepts a single input argument a, but if you look at the function call it was mymethod(2,3). We passed 2 input arguments 2 and 3. But, it worked out! That is what the decorator magically did. It transformed the two input arguments into a single argument and passed it to the original function. Thus, even when the function was called with 2 arguments, when it was actually executed it was given only one argument as input. The decorator made use of the string passed to it to convert the 2 arguments in function call to one argument before giving it to the function. A “*” passed to the decorator results in addition of the input arguments and anything else results in addition of the input arguments.
These are very simple examples to demonstrate what a decorator can actually do. If you want to understand where decorator can be implemented in real time code, please check Siddhartha’s example on Guard Implementation using Python Decorators.