Re-raise Python exception and preserve stack trace

PythonExceptionStack Trace

Python Problem Overview


I'm trying to catch an exception in a thread and re-raise it in the main thread:

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]

This basically works and yields the following output:

(<type 'exceptions.ValueError'>, ValueError('x',), <traceback object at 0x1004cc320>)
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    raise failingThread.exc_info[1]

However, the source of the exception points to line 16, where the re-raise occurred. The original exception comes from line 7. How do I have to modify the main thread so that the output reads:

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

Python Solutions


Solution 1 - Python

In Python 2 you need to use all three arguments to raise:

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

passing the traceback object in as the third argument preserves the stack.

From help('raise'):

> If a third object is present and not None, it must be a traceback > object (see section The standard type hierarchy), and it is > substituted instead of the current location as the place where the > exception occurred. If the third object is present and not a > traceback object or None, a TypeError exception is raised. The > three-expression form of raise is useful to re-raise an exception > transparently in an except clause, but raise with no expressions > should be preferred if the exception to be re-raised was the most > recently active exception in the current scope.

In this particular case you cannot use the no expression version.

For Python 3 (as per the comments):

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

or you can simply chain the exceptions using raise ... from ... but that raises a chained exception with the original context attached in the cause attribute and that may or may not be what you want.

Solution 2 - Python

This code snippet works in both python 2 & 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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionroskakoriView Question on Stackoverflow
Solution 1 - PythonDuncanView Answer on Stackoverflow
Solution 2 - PythonSteven ThanView Answer on Stackoverflow