How to globally customize exception stack traces in Python

The Python language has some useful flexibility when it comes to exceptions. Programmers want as much relevant information as we can get when our code does something unexpected. Python's default behavior for an unhandled exception is to print a stack trace, the error type, and the error description. This is sent to sys.stderr (typically, the console), like this:

>>> 1/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

That's useful, but perhaps in your code you'd prefer to see other specific data when there's a crash. For example, it may be helpful to see the values of the current local variables, or global variables. Python has not left you out in the cold here. In fact, Python makes it incredibly easy to send whatever you like to sys.stderr using a hook. During an unhandled exception, this is the function Python calls to print the error:

sys.excepthook(exc_type, value, traceback)

Which takes in these three values:

exc_type
the exception class, such as ZeroDivisonError
value
the exception instance that wasn't handled
traceback
a trackeback object; the same as what is stored in sys.last_traceback

Note that these are the same three ordered values returned by sys.exc_info() during an exception

You can create your own function which takes the same three arguments, make it do what you like, and assign it to the hook:

>>> def my_exchandler(exc_type, value, traceback):
>>>    print("Nothing to see here, folks. Everything's fine. Move along.", file=sys.stderr)
>>>
>>> import sys
>>> sys.excepthook = my_exchandler
>>>
>>> 1/0
Nothing to see here, folks. Everything's fine. Move along.

Or, if you wanted to display all local and global variables as mentioned earlier (which is slightly more useful than the modification above), do this:

>>> import sys, traceback
>>>
>>> def my_exchandler(exc_type, value, tb):
>>>    traceback.print_exception(exc_type, value, tb)
>>>    locals = True
>>>    for active_vars in [tb.tb_frame.f_locals, tb.tb_frame.f_globals]:
>>>        header = 'Locals:' if locals else 'Globals:'
>>>        print(header, file=sys.stderr)
>>>        for k, v in active_vars.items():
>>>            if not (k.startswith('__') and k.endswith('__')):
>>>                print(f'	{k} = {v}', file=sys.stderr)
>>>        locals = False
>>>
>>> sys.excepthook = my_exchandler
>>>
>>> my_var = 'suspicious value here?'
>>>
>>> 1/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Locals:
        my_var = 'suspicious value here?'
        traceback = <module 'traceback' from '/usr/lib/python3.2/traceback.pyc'>
        my_exchandler = <function my_exchandler at 0x89f895c>
        sys = <module 'sys' (built-in)>
Globals:
        my_var = 'suspicious value here?'
        traceback = <module 'traceback' from '/usr/lib/python2.7/traceback.pyc'>
        my_exchandler = <function my_exchandler at 0x89f895c>
        sys = <module 'sys' (built-in)>

But then how do you get the default behavior back? And what if you code a replacement function accidentally producing an exception itself? Not to worry, Python keeps a backup copy of the default hook, and uses it when needed. It's here:

sys.__excepthook__

You can use the backup copy to get back to the default behavior like this:

sys.excepthook = sys.__excepthook__

Posted 10/22/2013