16

I have a python function that may raise an exception. The caller catches the exception and deals with it. Now I would like to add a decorator to that function that also catches the exception, does some handling, but then re-raises the exception to allow the original caller to handle it. This works, except that when the original caller displays the call stack from the exception, it shows the line in the decorator where it was re-raised, not where it originally occurred. Example code:

import sys,traceback

def mydec(func):
    def dec():
        try:
            func()
        except Exception,e:
            print 'Decorator handled exception %s' % e
            raise e
    return dec

@mydec
def myfunc():
    x = 1/0

try:
    myfunc()
except Exception,e:
    print 'Exception: %s' % e
    type,value,tb = sys.exc_info()
    traceback.print_tb(tb)

The output is:

Decorator handled exception integer division or modulo by zero
Exception: integer division or modulo by zero
  File "allbug.py", line 20, in <module>
    myfunc()
  File "allbug.py", line 9, in dec
    raise e

I would like the decorator to be able to handle the exception but the traceback should indicate the x = 1/0 line rather than the raise line. How can I do this?

Graeme Perrow
  • 56,086
  • 21
  • 82
  • 121

2 Answers2

19

Just use raise; (i.e. do not raise anything specific, just raise;) in a catch block to re-raise the exception without "resetting" the traceback.

  • Just to be clear, just use a naked "raise" statement. Do not specify what to raise. – drxzcl Nov 22 '10 at 20:34
  • 1
    @Graeme: No need to smack your head. This isn't an obvious solution it could have thought of by itself :) –  Nov 22 '10 at 20:39
8

I just wrote a class similar to what you are doing, but with a little more options available. Here it is:

class ErrorIgnore(object):
   def __init__(self, errors, errorreturn = None, errorcall = None):
      self.errors = errors
      self.errorreturn = errorreturn
      self.errorcall = errorcall

   def __call__(self, function):
      def returnfunction(*args, **kwargs):
         try:
            return function(*args, **kwargs)
         except Exception as E:
            if type(E) not in self.errors:
               raise E
            if self.errorcall is not None:
               self.errorcall(E, *args, **kwargs)
            return self.errorreturn
      return returnfunction

Common usage would be something like:

def errorcall(E, *args):
   print 'exception skipped', E

@ErrorIgnore(errors = [ZeroDivisionError, ValueError], errorreturn = None, errorcall = errorcall)
def myfunction(stuff):
   # do stuff
   # return stuff
   # the errors shown are skipped
Garrett Berg
  • 2,585
  • 1
  • 22
  • 21