Как поймать исключение в декораторе, но разрешить вызывающему и его поймать?

У меня есть функция python, которая может вызвать исключение. Вызывающий получает исключение и имеет дело с ним. Теперь я хотел бы добавить декоратор к этой функции, которая также ловит исключение, выполняет некоторую обработку, но затем повторно вызывает исключение, чтобы позволить первому вызывающему пользователю обработать его. Это работает, за исключением того, что, когда исходный вызывающий абонент отображает стек вызовов из исключения, он показывает строку в декораторе, где он был повторно поднят, а не там, где он первоначально произошел. Пример кода:

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)

Вывод:

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

Я хотел бы, чтобы декоратор мог обрабатывать исключение, но трассировка должна указывать на строку x = 1/0, а не на строку raise. Как я могу это сделать?

Ответы

Ответ 1

Просто используйте raise; (т.е. не создавайте ничего конкретного, просто raise;) в блоке catch, чтобы повторно поднять исключение без "сброса" трассировки.

Ответ 2

Я просто написал класс, похожий на то, что вы делаете, но с немного больше доступных опций. Вот он:

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

Общее использование будет выглядеть примерно так:

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