Python Decorator

This is a quick tutorial on the Decorator pattern in Python. You probably need some basic Python to follow along, but I will try to keep it simple.

The decorator pattern in programming, no matter the language, is all about adding functionality or behaviour to something else. It can be used in the data sense where I have a lot of data and I want to decorate the data with a new column. I could also have an Object and I want to add new functionality to that object without affecting the current behaviour of that object. Now would it not be great if we could wrap functionality around an existing object? Well that is exactly what the python decorator does.

Let us start with a simple object, recall everything in python can be an object so a function is just an object with behaviour.




def listAdd(l:list):

"""Takes a list and sums its contents"""

return sum(l)



myList
=[1,3,4,5,2]

print(listAdd(myList))


the result will be 15

This simple function just takes a list l and returns the sum of the contents of that list.

What we want to do is add functionality that is called before we call listAdd and After we call listAdd. We could also calculate the max of the list while we are at it. None of this functionality currently exists in the function now.

You could just update the function to provide the functionality but what if you had lots of different types of functions and you wanted to wrap each one of them with the new functionality. It would not be very code reuse friendly if we updated each function. It is better to write the functionality and then wrap the function with the new functionality.




Firstly let us look at how we can wrap the function up so we can call a print before and after the listAdd function. To do this we can use a simple decorator like so




def decorator(func):

# We can use any name apart from decorateras long as its consistent
# func here is the function to call listAdd for thsi example



def wrapper(*args, **kwargs): # Pass along and methods args or Keyword args
print("Func " + str(args))
print("Before calling " + func.__name__)
res
= func(*args, **kwargs) # Call the func ie listAdd with the args and keyword args
print("After calling " + func.__name__)
print("Result is ="+ str(res))
return res # Tell the caller what the result of teh function was

return wrapper
#from within the decorater func we now need to call the method wrapping func



@decorator #Annotation that must be the name of the decorator function
def listAdd(l:list):

"""Takes a list and sums its contents"""
return sum(l)



myList
=[1,3,4,5,2]

print(listAdd(myList))







The result of this output will be

Func ([1, 3, 4, 5, 2],)

Before calling listAdd

After calling listAdd

Result is  = 15

15



So, let us step through the code and see what it is doing. Firstly, we create a decorator function which takes an argument func. The argument is the actual function you are trying to wrap which in this case is listAdd but it can be anything, recall everything is an object.

Now inside the decorater function we have another defined function called wrapper. This function is called wrapper and takes 2 arguments the *args and **kwargs. A quick explanation is probably needed here.

*args are the arguments that you pass to a method, for example method(1,2) has 2 arguments this is represented by *args

**kwargs is a dictionary for example a method(x=1,y=1) will have no args but will have keyword arguments the keys are x and y and the values respectively are 1 and 1.

Now we have the arguments that we need for calling listAdd, which are all in *args unless you call it as listAdd(l=myList) then it will be in **kwargs where key is l value is myList we call the function listAdd which is symbolically represented as func here.

Now you can do your print statements

Call the function passing in *args and **kwargs saving result in res

Call a print statement after calling listAdd

Then return the result

The decorator function then runs 1 line of code which is return wrapper this calls the function wrapper which does all the work and returns.

Now to get this to work on any function you just need to literally decorate the function to be wrapped with an annotation




def decorator(func):

# We can use any name apart from decorateras long as its consistent
# func here is the function to call listAdd for thsi example

def maxCalc(*args, **kwargs):

for k,v in kwargs.items() :
print ("**kwargs="+str(k) + " " + str(v) )

for arg in args:
print ("Args="+str(arg) )

if(len(args)==0):
return max(kwargs['l'])

else:
return max(*args)


def wrapper(*args, **kwargs): # Pass along and methods args or Keyword args

print("Func " + str(args))
for k,v in kwargs.items() :
print (str(k) + " " + str(v) )

print("Before calling " + func.__name__)

res
= func(*args, **kwargs) # Call the func ie listAdd with the args and keyword args

print("After calling " + func.__name__)

print("Result is ="+ str(res))

wrapper
.sum=res

wrapper
.max=maxCalc(*args, **kwargs)

return res # Tell the caller what the result of teh function was

return wrapper

#from within the decorater func we now need to call the method wrapping func



@decorator #Annotation that must be the name of the decorator function

def listAdd(l:list):

"""Takes a list and sums its contents"""

return sum(l)



myList
=[1,3,4,5,2]

result
=listAdd(l=myList)

print("Final Result ="+str(result) )

print("Result of ListAdd with decorater is "+str(listAdd.sum) + " the max is "+str(listAdd.max) )




The results of this code will be:
Func ()
l [1, 3, 4, 5, 2]
Before calling listAdd
After calling listAdd
Result is =15
**kwargs=l [1, 3, 4, 5, 2]
Final Result =15
Result of ListAdd with decorater is 15 the max is 5



We have significantly expanded the code but let us walk through what changed. Now we create an on the fly variable sum and another variable max which is on the wrapper which is just wrapping the func which is itself listAdd.

We then call a new function in decorater which calculates the max again passing the *args and **kwargs. Now this is where it gets interesting because while listAdd can make sense of these args/kwargs we are now on our own and must extract the argument as we are merely using max(…) so we need to manually check if it is called as args or it is using the kwarg format ie listAdd(l=<…> )

Then we calc max and store it.

We can then retrieve it later

I hope this short course has given you some idea about how to create a decorator and how it can help you while you develop code.



I just wanted to throw this in as we are talking about statistics regarding lists. If you know Numpy this is trivial to do but it does not really teach you about how to do a decorator. However you could try to add the statistics code into this decorator to effectively return a tuple of stats. like ( sum, mean, max) the code below is how you could do this easily.




import numpy as np
import collections

def decorator(func):

# We can use any name apart from decorateras long as its consistent
# func here is the function to call listAdd for thsi example



def stats(*args, **kwargs):

for k,v in kwargs.items() :
print ("**kwargs="+str(k) + " " + str(v) )



for arg in args:
print ("Args="+str(arg) )

if(len(args)==0):
#return max(kwargs['l'])
l
=np.array(kwargs['l'])
stats
= collections.namedtuple('Stats', 'sum mean max')
s
=stats(l.sum(), l.mean(), l.max())
return s

else:
#return max(*args)
l
=np.array(*args)
stats
= collections.namedtuple('Stats', 'sum mean max')
s
=stats(l.sum(), l.mean(), l.max())
return s





def wrapper(*args, **kwargs): # Pass along and methods args or Keyword args
print("Func " + str(args))

for k,v in kwargs.items() :
print (str(k) + " " + str(v) )

print("Before calling " + func.__name__)
res
= func(*args, **kwargs) # Call the func ie listAdd with the args and keyword args
print("After calling " + func.__name__)
print("Result is ="+ str(res))
wrapper
.sum=res
wrapper
.stats=stats(*args, **kwargs)
return res # Tell the caller what the result of the function was

return wrapper

#from within the decorater func we now need to call the method wrapping func



@decorator #Annotation that must be the name of the decorator function
def listAdd(l:list):

"""Takes a list and sums its contents"""

return sum(l)



myList
=[1,3,4,5,2]

result
=listAdd(myList)

print("Final Result ="+str(result) )

print("Result of ListAdd with decorater is "+str(listAdd.sum) + " the max is "+str(listAdd.stats) )




The results of this code will be:
Func ([1, 3, 4, 5, 2],)
Before calling listAdd
After calling listAdd
Result is =15
Args=[1, 3, 4, 5, 2]
Final Result =15
Result of ListAdd with decorator is 15 the max is Stats(sum=15, mean=3.0, max=5)


People who enjoyed this article also enjoyed the following:


Python Context Management Protocol - using the with statement
Naive Bayes classification AI algorithm
K-Means Clustering AI algorithm
Equity Derivatives tutorial
Fixed Income tutorial

And the following Trails:

C++
Java
python
Scala
Investment Banking tutorials

HOME
homeicon




By clicking Dismiss you accept that you may get a cookie that is used to improve your user experience and for analytics.
All data is anonymised. Our privacy page is here =>
Privacy Policy
This message is required under GDPR (General Data Protection Rules ) and the ICO (Information Commissioners Office).