El rastreo de excepciones se oculta si no se vuelve a subir inmediatamente

Tengo un código similar a este:

import sys def func1(): func2() def func2(): raise Exception('test error') def main(): err = None try: func1() except: err = sys.exc_info()[1] pass # some extra processing, involving checking err details (if err is not None) # need to re-raise err so caller can do its own handling if err: raise err if __name__ == '__main__': main() 

Cuando func2 genera una excepción, recibo el siguiente rastreo:

 Traceback (most recent call last): File "err_test.py", line 25, in  main() File "err_test.py", line 22, in main raise err Exception: test error 

Desde aquí no veo de dónde viene la excepción. El rastro original se pierde.

¿Cómo puedo conservar el rastreo original y volver a subirlo? Quiero ver algo similar a esto:

 Traceback (most recent call last): File "err_test.py", line 26, in  main() File "err_test.py", line 13, in main func1() File "err_test.py", line 4, in func1 func2() File "err_test.py", line 7, in func2 raise Exception('test error') Exception: test error 

Un raise blanco plantea la última excepción.

 # need to re-raise err so caller can do its own handling if err: raise 

Si usas el comando raise something Python no tiene forma de saber si something fue una excepción que se acaba de detectar, o una nueva excepción con un nuevo rastreo de stack. Es por eso que hay un raise blanco que conserva la traza de stack.

Referencia aqui

Es posible modificar y volver a emitir una excepción:

Si no hay expresiones presentes, raise re-eleva la última excepción que estaba activa en el scope actual. Si no hay ninguna excepción activa en el scope actual, se TypeError una excepción TypeError que indica que se trata de un error (si se ejecuta bajo IDLE, se Queue.Empty una excepción Queue.Empty ).

De lo contrario, raise evalúa las expresiones para obtener tres objetos, utilizando None como el valor de las expresiones omitidas. Los primeros dos objetos se utilizan para determinar el tipo y el valor de la excepción.

Si un tercer objeto está presente y no es None , debe ser un objeto de rastreo (consulte la sección La jerarquía de tipos estándar), y se sustituye en lugar de la ubicación actual como el lugar donde se produjo la excepción. Si el tercer objeto está presente y no es un objeto de rastreo o None , se TypeError una excepción TypeError .

La forma de raise de tres expresiones es útil para volver a elevar una excepción de forma transparente en una cláusula de except , pero se debería preferir el raise sin expresiones si la excepción que se vuelve a elevar fue la excepción activa más reciente en el ámbito actual.

Entonces, si desea modificar la excepción y volver a lanzarla, puede hacer esto:

 try: buggy_code_which_throws_exception() except Exception as e: raise Exception, "The code is buggy: %s" % e, sys.exc_info()[2] 

Puede obtener mucha información sobre la excepción a través de sys.exc_info() junto con el módulo de rastreo

Prueba la siguiente extensión de tu código.

 import sys import traceback def func1(): func2() def func2(): raise Exception('test error') def main(): try: func1() except: exc_type, exc_value, exc_traceback = sys.exc_info() # Do your verification using exc_value and exc_traceback print "*** print_exception:" traceback.print_exception(exc_type, exc_value, exc_traceback, limit=3, file=sys.stdout) if __name__ == '__main__': main() 

Esto imprimiría, similar a lo que querías.

 *** print_exception: Traceback (most recent call last): File "err_test.py", line 14, in main func1() File "err_test.py", line 5, in func1 func2() File "err_test.py", line 8, in func2 raise Exception('test error') Exception: test error 

Si bien la respuesta de @Jochen funciona bien en el caso simple, no es capaz de manejar casos más complejos, en los que no se captura ni se vuelve a generar de manera directa, pero, por alguna razón, se les da la excepción como objeto y desean relanzarlos completamente. Nuevo contexto (es decir, si necesita manejarlo en un proceso diferente).

En este caso, propongo lo siguiente:

  1. obtener el exc_info original
  2. formatear el mensaje de error original, con seguimiento de stack
  3. lanzar una nueva excepción con ese mensaje de error completo (seguimiento de stack incluido) incrustado

Antes de hacer esto, defina un nuevo tipo de excepción que volverá a realizar más adelante …

 class ChildTaskException(Exception): pass 

En el código infractor …

 import sys import traceback try: # do something dangerous except: error_type, error, tb = sys.exc_info() error_lines = traceback.format_exception(error_type, error, tb) error_msg = ''.join(error_lines) # for example, if you are doing multiprocessing, you might want to send this to another process via a pipe connection.send(error_msg) 

Rethrow …

 # again, a multiprocessing example of receiving that message through a pipe error_msg = pcon.recv() raise ChildTaskException(error_msg) 

Su función principal debe verse así:

 def main(): try: func1() except Exception, err: # error processing raise 

Esta es la forma estándar de manejar (y volver a subir) los errores. Aquí hay una demostración de teclado.