Using decorators to write a generic exception handler
Let’s write a realistic example of the use of decorators trying to improve our understanding of them.
In the following program we define a function called divide which is implement the trivial functionality of dividing the two parameters that are passed to it:
def divide(x,y): return x / y
Nothing fancy or useful of course, but enough to make apparent the use of decorator as we will see here.
If the divide function is called with y equalling 0 obviously it will throw an exception as we can see here:
#!/usr/bin/python def divide(x,y): return x / y print divide(8,0)
Running this program will result to the following output:
Traceback (most recent call last): File "./junk1.py", line 6, in print 8/0 ZeroDivisionError: integer division or modulo by zero
The obvious way to fix this behaviour is to catch this exception showing some message to the user:
#!/usr/bin/python def divide(x,y): try: a = x/y return x / y except Exception as ex: print ex print divide(8,0)
To make our example more realistic, lets assume that we need another function requiring similar exception catching behaviour, something like this:
#!/usr/bin/python def print_file(filename): try: f = open(filename) print f.read() except Exception as ex: print ex def divide(x,y): try: a = x/y return x / y except Exception as ex: print ex print_file('nonexistent_file')
In this case, assuming nonexistent_file indeed does not exist, we except to see the following output when we run the program:
[Errno 2] No such file or directory: 'nonexistent_file'
Sure, we have a working program that can handle possible exceptions reasonably well but as you can see the same code needed to handle them is repeated in both functions. The use of a simple decorator can make our code more expressible and easier to understand and this is how this can be done:
#!/usr/bin/python def handle_exceptions(foo): def inner(*args, **kargs): try: foo(*args, **kargs) except Exception as ex: print ex return inner @handle_exceptions def print_file(filename): f = open(filename) print f.read() @handle_exceptions def divide(x,y): a = x/y return x / y print_file('nonexistent_file')
Note, how our code is now cleaner as the print_file and divide functions are no longer polluted with the exception handling logic which is now implemented in a decorator making our code easier to read and understand.
One question that arises here, is what if did not want one of the functions to actually display the exception message? How in other words we can customize the decorator passing to it additional parameters to alter its behaviour? This will be the topic of the next example that you can read in the following page.