Exception traceback is hidden if not re-raised immediately

PythonExceptionException HandlingTry CatchTraceback

Python Problem Overview


I've got a piece of code similar to this:

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()

When func2 raises an exception I receive the following traceback:

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

From here I don't see where the exception is coming from. The original traceback is lost.

How can I preserve original traceback and re-raise it? I want to see something similar to this:

Traceback (most recent call last):
  File "err_test.py", line 26, in <module>
    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

Python Solutions


Solution 1 - Python

A blank raise raises the last exception.

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

If you use raise something Python has no way of knowing if something was an exception just caught before, or a new exception with a new stack trace. That's why there is the blank raise that preserves the stack trace.

Reference here

Solution 2 - Python

It is possible to modify and rethrow an exception:

> If no expressions are present, raise re-raises the last exception that > was active in the current scope. If no exception is active in the > current scope, a TypeError exception is raised indicating that this is > an error (if running under IDLE, a Queue.Empty exception is raised > instead). > > Otherwise, raise evaluates the expressions to get three objects, using > None as the value of omitted expressions. The first two objects are > used to determine the type and value of the exception. > > 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.

So if you want to modify the exception and rethrow it, you can do this:

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

Solution 3 - Python

You can get a lot of information about the exception via the sys.exc_info() along with the traceback module

try the following extension to your code.

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()

This would print, similar to what you wanted.

*** 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

Solution 4 - Python

While @Jochen's answer works well in the simple case, it is not capable of handling more complex cases, where you are not directly catching and rethrowing, but are for some reason given the exception as an object and wish to re-throw in a completely new context (i.e. if you need to handle it in a different process).

In this case, I propose the following:

  1. get the original exc_info
  2. format the original error message, with stack trace
  3. throw a new exception with that full error message (stack trace incl.) embedded

Before you do this, define a new exception type that you will rethrow later...

class ChildTaskException(Exception):
    pass

In the offending code...

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)

Solution 5 - Python

Your main function needs to look like this:

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

This is the standard way of handling (and re-raising) errors. http://codepad.org/gc1U5487">Here is a codepad demonstration.

Solution 6 - Python

In Python 3:

import sys

class CustomError(Exception):
    pass

try:
    code_throwing_an_exception()
except Exception as e:
    _, value, traceback = sys.exc_info()
    raise CustomError("A new Exception was raised: %s" % value).with_traceback(traceback)

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
QuestionparxierView Question on Stackoverflow
Solution 1 - PythonJochen RitzelView Answer on Stackoverflow
Solution 2 - PythonqrisView Answer on Stackoverflow
Solution 3 - PythonSenthil KumaranView Answer on Stackoverflow
Solution 4 - Pythontvt173View Answer on Stackoverflow
Solution 5 - PythonGabi PurcaruView Answer on Stackoverflow
Solution 6 - PythonOmid AriyanView Answer on Stackoverflow