Volver a elevar la excepción de Python y conservar el seguimiento de la stack

Estoy intentando capturar una excepción en un hilo y volver a subirla en el hilo principal:

import threading import sys class FailingThread(threading.Thread): def run(self): try: raise ValueError('x') except ValueError: self.exc_info = sys.exc_info() failingThread = FailingThread() failingThread.start() failingThread.join() print failingThread.exc_info raise failingThread.exc_info[1] 

Esto básicamente funciona y produce el siguiente resultado:

 (, ValueError('x',), ) Traceback (most recent call last): File "test.py", line 16, in  raise failingThread.exc_info[1] 

Sin embargo, la fuente de la excepción apunta a la línea 16, donde ocurrió la re-subida. La excepción original proviene de la línea 7. ¿Cómo debo modificar el hilo principal para que la salida muestre:

 Traceback (most recent call last): File "test.py", line 7, in  

En Python 2 necesitas usar los tres argumentos para plantear:

 raise failingThread.exc_info[0], failingThread.exc_info[1], failingThread.exc_info[2] 

pasar el objeto de rastreo como el tercer argumento conserva la stack.

De la help('raise') :

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 excepción, 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.

En este caso particular, no puede utilizar la versión sin expresión.

Para Python 3 (según los comentarios):

 raise failingThread.exc_info[1].with_traceback(failingThread.exc_info[2]) 

o simplemente puede encadenar las excepciones usando raise ... from ... pero eso genera una excepción encadenada con el contexto original adjunto en el atributo de la causa y eso puede o no ser lo que desea.

Este fragmento de código funciona tanto en python 2 como en 3:

  1 try: ----> 2 raise KeyError('Default key error message') 3 except KeyError as e: 4 e.args = ('Custom message when get re-raised',) #The comma is not a typo, it's there to indicate that we're replacing the tuple that e.args pointing to with another tuple that contain the custom message. 5 raise 

¿Podrías escribirlo así?

 try: raise ValueError('x') except ValueError as ex: self.exc_info = ex 

y luego usar el seguimiento de stack de la excepción?