Welcome, guest | Sign In | My Account | Store | Cart

A basic exception handling idiom using decorators which allows you to re-use the exception handler for different functions, while customizing your handlers using arguments passed to an exception handling "decorator".

Python, 79 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/python2.4

# An exception handling idiom using decorators.

__author__ = "Anand Pillai"

def ExpHandler(*posargs):

    def nestedhandler(func,exptuple, *pargs, **kwargs):
        """ Function that creates a nested exception handler from
        the passed exception tuple """

        exp, handler = exptuple[0]
        try:
            if len(exptuple)==1:
                func(*pargs, **kwargs)
            else:
                nestedhandler(func,exptuple[1:], *pargs, **kwargs)
        except exp, e:
            if handler:
                handler(e)
            else:
                print e.__class__.__name__,':',e                
        
    def wrapper(f):
        def newfunc(*pargs, **kwargs):
            if len(posargs)<2:
                t = tuple(item for item in posargs[0] if issubclass(item,Exception) or (Exception,))
                try:
                    f(*pargs, **kwargs)
                except t, e:
                    print e.__class__.__name__,':',e
            else:
                t1, t2 =posargs[0], posargs[1]
                l=[]
                for x in xrange(len(t1)):
                    try:
                        l.append((t1[x],t2[x]))
                    except:
                        l.append((t1[x],None))

                # Reverse list so that exceptions will
                # be caught in order.
                l.reverse()
                t = tuple(l)
                nestedhandler(f,t,*pargs,**kwargs)
                    
        return newfunc

    return wrapper

def myhandler(e):
    print 'Caught exception!', e
    
# Examples
# Specify exceptions in order, first one is handled first
# last one last.
@ExpHandler((ZeroDivisionError,ValueError), (None,myhandler))
def f1():
    1/0

@ExpHandler((TypeError, ValueError, StandardError), (myhandler,)*3)
def f2(*pargs, **kwargs):
    print pargs
    x = pargs[0]
    y = x[0]
    y += x[1]

@ExpHandler((ValueError, Exception))
def f3(*pargs):
    l = pargs[0]
    return l.index(10)

if __name__=="__main__":
    f1()
    # Calls exception handler
    f2('Python', 1)
    # Calls exception handler
    f3(range(5),)
    

Very often one tends to code functions in Python without wiring in the exception handling mechanism then and there. Also, sometimes one requires to add exception handlers to functions imported from other modules, which have not been taken care of already by the module author.

Sometimes this can seem a bit tedious, requiring to write the try... catch blocks for all the functions. If you are using Python 2.4, the above exception handling idiom using a decorator allows you to re-use the same exception handling code without needing to write specific handlers for each function. It also takes care of the order of exception handling.

For example, if your function "myfunc" might raise IndexError, KeyError and ValueError in that order...

@ExpHandler((IndexError, KeyError, ValueError)) def myfunc(pargs, *kwargs): ....

The handler also takes care of positional and keyword arguments of the wrapped up function.

Mod: 06/04/05

This adds "real" exception handling to the recipe instead of just exception "trapping". If you want to handle an exception, pass its handler to the decorator in a second tuple argument in the same order as the exceptions.

For example, to handle "TypeError" using "handler1" and "ValueError" using "handler2" for "myfunc" in that order,

@ExpHandler((TypeError, ValueError), (handler1, handler2)) def myfunc(pargs, *kwargs): ...

If the second tuple argument is not given this recipe defaults to pure exception trapping. Same happens if the handler is given, but is None.

If the same handler (say "handler") is used to handle all exceptions, this allows you to write nifty code like,

@ExpHandler((Exception1, Exception2,Exception3), (handler,)3) def myfunc(pargs, **kwargs): ...

Note that this recipe uses nested exception handling when handlers are specified, with the first exception specified nested deepest.

I could not find a way to write this by just using cascaded exceptions ( try: func(...) except Exception1, e: handler1(e) except Exception2, e: handler2(e) .... ) which would be the best approach. This is because a recursive function when passed a list of exceptions can simulate a nested exception handler, but it is not that straightforward to write a cascaded exception handler using a function and a list of exception tuples.

4 comments

Steven Cummings 16 years, 8 months ago  # | flag

Not really "handling" but close.. I like this recipe a lot, but it's really just trapping and printing the indicated exception types, not handling in general. So I would want to enhance this recipe with a means of confuring and providing handler code. The simplest idea would be to change the decorator argument to a dict (**posargs) and where the keys are the exception types that each map to their own callable handler. I'm sure somebody could come up with something better but that would be a start.

Steven Cummings 16 years, 8 months ago  # | flag

RE: Not really "handling" but close.. I know I should have made in more clear in my comment, but I was in fact sort of responding to the last statement in your recipe discussion, not overlooking it.

Andreas Kloss 16 years, 8 months ago  # | flag

Even better: Move the posargs parsing out into the wrapper (as it does not need to be done more than once per "decorating"), use a generator display instead of a list comprehension (notice I skipped the [] within tuple()) and use shortchanging or instead of if/then. Also call f correctly with positional and keyword arguments, which requires no if/then either.

def ExpHandler(*posargs):
    """exception handling decorator"""
    def wrapper(f):
        t = tuple(item for item in posargs[0]
            if issubclass(item,Exception)) or (Exception,)
        def newfunc(*pargs, **kwargs):
            try:
                f(*pargs, **kwargs)
            except t, e:
                # Only inform the user that the exception occured
                print e.__class__.__name__,':',e
        return newfunc
    return wrapper
Vaibhav Bhatia 15 years, 1 month ago  # | flag

rewritten using functools.partial. The logic of the code remains same here, however partial is used here and default Exception class is added when no input is given. I have also made some modifications in the way handlers and exception classes are passed to the decorator.

#!/usr/bin/env python2.5

import functools
def ExpHandler(*pargs):
    """ An exception handling idiom using decorators"""

    def wrapper(f):
        if pargs:
            (handler,li) = pargs
            t = [(ex, handler)
                 for ex in li ]
            t.reverse()
        else:
            t = [(Exception,None)]

        def newfunc(t,*args, **kwargs):
            ex, handler = t[0]

            try:
                if len(t) == 1:
                    f(*args, **kwargs)
                else:
                    newfunc(t[1:],*args,**kwargs)
            except ex,e:
                if handler:
                    handler(e)
                else:
                    print e.__class__.__name__, ':', e

        return functools.partial(newfunc,t)
    return wrapper
def myhandler(e):
    print 'Caught exception!', e

# Examples
# Specify exceptions in order, first one is handled first
# last one last.

@ExpHandler(myhandler,(ZeroDivisionError,))
@ExpHandler(None,(AttributeError, ValueError))
def f1():
    1/0

@ExpHandler()
def f3(*pargs):
    l = pargs
    return l.index(10)

if __name__=="__main__":
    f1()
    f3()
Created by Anand on Sun, 3 Apr 2005 (PSF)
Python recipes (4591)
Anand's recipes (38)

Required Modules

  • (none specified)

Other Information and Tasks