57

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>
roskakori
  • 3,139
  • 1
  • 30
  • 29
  • awesome, I've been re-raising exceptions from other threads too, but never went as far you want to :) – Dima Tisnek Aug 14 '13 at 09:18
  • possible duplicate of ["Inner exception" (with traceback) in Python?](http://stackoverflow.com/questions/1350671/inner-exception-with-traceback-in-python) – Mr_and_Mrs_D May 30 '15 at 16:20

2 Answers2

54

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.

Duncan
  • 92,073
  • 11
  • 122
  • 156
  • 6
    @AndyHayden Indeed, in Python 3 [raise](http://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) would have to be called somehow like `raise failingThread.exc_info[0](failingThread.exc_info[1]).with_traceback(failingThread.exc_info[2])`. Although in Python 3 the `raise AnotherError from stored_exception` might provide an even better output – Tobias Kienzler Aug 12 '13 at 14:27
  • 1
    correction, the first one should be `raise failingThread.exc_info[1].with_traceback(failingThread.exc_info[2])` as per [this comment](http://stackoverflow.com/questions/18188563/how-to-re-raise-an-exception-in-nested-try-except-blocks/18188660?noredirect=1#comment26654548_18188660) – Tobias Kienzler Aug 12 '13 at 14:56
  • @TobiasKienzler Would you mind either extrapolating your highly upvoted comments into a new answer *or* editing them into the current answer? In the post-Python 2 era, Python 2-only solutions are sadly obsolete. – Cecil Curry Apr 13 '18 at 05:10
  • 1
    @CecilCurry Good suggestion, I updated my answer. Surely though you have enough privilege you could have just edited it directly? – Duncan Apr 13 '18 at 10:52
3

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
Steven Than
  • 396
  • 1
  • 2
  • 6
  • 2
    I can guess why the downvote. This solution is only valid if there wasn't any exception handling. Imagine that upon catching the exception you try to update a status in the db and that fails as well. In that case the exception that will be raised is the last one (db transaction failed) and not the one we initially caught. – odedfos Sep 10 '17 at 10:26