2012-01-25 15 views
19

Estoy escribiendo un decorador para aplicar a una función. Debería detectar cualquier excepción y luego generar una excepción personalizada basada en el mensaje de excepción original. (Esto se debe a que suds arroja una excepción genérica WebFault, desde cuyo mensaje analizo la excepción lanzada por el servicio web y planteo una excepción de Python para duplicarla).Python: Decorador de excepciones. Cómo conservar stacktrace

Sin embargo, cuando elevo la excepción personalizada en el contenedor, quiere que el stacktrace apunte a la función que generó la excepción original de WebFault. Lo que hasta ahora he planteado es la excepción correcta (analiza dinámicamente el mensaje y crea la clase de excepción). Mi pregunta es: ¿Cómo puedo conservar el stacktrace para apuntar a la función original que generó la excepción WebFault?

from functools import wraps 

def try_except(fn): 
     def wrapped(*args, **kwargs): 
      try: 
       fn(*args, **kwargs) 
      except Exception, e: 
       parser = exceptions.ExceptionParser() 
       raised_exception = parser.get_raised_exception_class_name(e) 
       exception = getattr(exceptions, raised_exception) 
       raise exception(parser.get_message(e)) 
     return wraps(fn)(wrapped) 
+1

¿Ha mirado el 'traceback' módulo? http://docs.python.org/library/traceback.html – stderr

+0

Al envolver en un decorador use [functools.wrap] (https://docs.python.org/2/library/functools.html) –

+0

posible duplicado de ["Excepción interna" (con traceback) en Python?] (Http://stackoverflow.com/questions/1350671/inner-exception-with-traceback-in-python) –

Respuesta

37

En Python 2.x, una característica poco conocida de raise es que puede ser utilizado con más de un argumento: la forma de tres argumentos de raise toma el tipo de excepción, la instancia de excepción y la rastrear. Puede acceder al traceback con sys.exc_info(), que devuelve (no por casualidad) el tipo de excepción, la instancia de excepción y el traceback.

(La razón de esto trata el tipo de excepción y la instancia de excepción como dos argumentos separados es un artefacto de los días antes de clases de excepciones.)

Así:

import sys 

class MyError(Exception): 
    pass 

def try_except(fn): 
    def wrapped(*args, **kwargs): 
     try: 
      return fn(*args, **kwargs) 
     except Exception, e: 
      et, ei, tb = sys.exc_info() 
      raise MyError, MyError(e), tb 
    return wrapped 

def bottom(): 
    1/0 

@try_except 
def middle(): 
    bottom() 

def top(): 
    middle() 

>>> top() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "tmp.py", line 24, in top 
    middle() 
    File "tmp.py", line 10, in wrapped 
    return fn(*args, **kwargs) 
    File "tmp.py", line 21, in middle 
    bottom() 
    File "tmp.py", line 17, in bottom 
    1/0 
__main__.MyError: integer division or modulo by zero 

En Python 3, esto cambió un poco. Allí, los rastreos se adjunta a la instancia de excepción en su lugar, y tienen un método with_traceback:

raise MyError(e).with_traceback(tb) 

Por otro lado Python 3 también tiene una excepción encadenamiento, que tiene más sentido en muchos casos; para usar eso, solo usaría:

raise MyError(e) from e 
+0

¡Genial, gracias! Esto hizo el trabajo. El único problema fue que tuve que mover la declaración del decorador al mismo archivo que la función que se estaba decorando; de lo contrario, devolvió sys.exc_info() (None, None, None) - ¿Alguna idea de por qué esto sería? – igniteflow

+0

Eso ... no tiene sentido. sys.exc_info() no importa dónde se define el llamador. Devuelve la excepción que se está manejando actualmente. Parece que el decorador que tenía en un archivo separado no estaba haciendo lo correcto, pero es difícil de decir sin ver el código real. –

4

He enfrentado este problema con pruebas que fueron decoradas con mis decoradores personalizados.

que utiliza después de construir en el cuerpo decorador para conservar traza original impreso en la salida unittests:

try: 
    result = func(self, *args, **kwargs) 
except Exception: 
    exc_type, exc_instance, exc_traceback = sys.exc_info() 
    formatted_traceback = ''.join(traceback.format_tb(
     exc_traceback)) 
    message = '\n{0}\n{1}:\n{2}'.format(
     formatted_traceback, 
     exc_type.__name__, 
     exc_instance.message 
    ) 
    raise exc_type(message) 
+0

sí, pero ¿cuáles son los argumentos para el decorador? –

+0

Solo para señalar que la diferencia con el código anterior es 'traceback.format_tb'. Gracias. – Ehvince

Cuestiones relacionadas