172

Given an Exception object (of unknown origin) is there way to obtain its traceback? I have code like this:

def stuff():
   try:
       .....
       return useful
   except Exception as e:
       return e

result = stuff()
if isinstance(result, Exception):
    result.traceback <-- How?

How can I extract the traceback from the Exception object once I have it?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
georg
  • 211,518
  • 52
  • 313
  • 390

6 Answers6

128

The answer to this question depends on the version of Python you're using.

In Python 3

It's simple: exceptions come equipped with a __traceback__ attribute that contains the traceback. This attribute is also writable, and can be conveniently set using the with_traceback method of exceptions:

raise Exception("foo occurred").with_traceback(tracebackobj)

These features are minimally described as part of the raise documentation.

All credit for this part of the answer should go to Vyctor, who first posted this information. I'm including it here only because this answer is stuck at the top, and Python 3 is becoming more common.

In Python 2

It's annoyingly complex. The trouble with tracebacks is that they have references to stack frames, and stack frames have references to the tracebacks that have references to stack frames that have references to... you get the idea. This causes problems for the garbage collector. (Thanks to ecatmur for first pointing this out.)

The nice way of solving this would be to surgically break the cycle after leaving the except clause, which is what Python 3 does. The Python 2 solution is much uglier: you are provided with an ad-hoc function,sys.exc_info(), which only works inside the except clause. It returns a tuple containing the exception, the exception type, and the traceback for whatever exception is currently being handled.

So if you are inside the except clause, you can use the output of sys.exc_info() along with the traceback module to do various useful things:

>>> import sys, traceback
>>> def raise_exception():
...     try:
...         raise Exception
...     except Exception:
...         ex_type, ex, tb = sys.exc_info()
...         traceback.print_tb(tb)
...     finally:
...         del tb
... 
>>> raise_exception()
  File "<stdin>", line 3, in raise_exception

But as your edit indicates, you're trying to get the traceback that would have been printed if your exception had not been handled, after it has already been handled. That's a much harder question. Unfortunately, sys.exc_info returns (None, None, None) when no exception is being handled. Other related sys attributes don't help either. sys.exc_traceback is deprecated and undefined when no exception is being handled; sys.last_traceback seems perfect, but it appears only to be defined during interactive sessions.

If you can control how the exception is raised, you might be able to use inspect and a custom exception to store some of the information. But I'm not entirely sure how that would work.

To tell the truth, catching and returning an exception is kind of an unusual thing to do. This might be a sign that you need to refactor anyway.

senderle
  • 145,869
  • 36
  • 209
  • 233
  • I agree that returning exceptions is somehow unconventional, but see [my other question](http://stackoverflow.com/q/11366892/989121) for some rationale behind this. – georg Jul 10 '12 at 17:13
  • @thg435, ok, this is making more sense then. Consider my above solution using `sys.exc_info` in conjunction with the [callback approach](http://stackoverflow.com/a/11419139/577088) I suggest on your other question. – senderle Jul 10 '12 at 18:09
  • 1
    More info (there is very little) on traceback objects: https://docs.python.org/3/library/types.html#types.TracebackType https://docs.python.org/3/reference/datamodel.html#traceback-objects – 0xfede7c8 Jun 29 '20 at 19:40
  • And the value of tracebackobj should be? – matanster Jul 07 '23 at 13:19
107

Since Python 3.0[PEP 3109] the built in class Exception has a __traceback__ attribute which contains a traceback object (with Python 3.2.3):

>>> try:
...     raise Exception()
... except Exception as e:
...     tb = e.__traceback__
...
>>> tb
<traceback object at 0x00000000022A9208>

The problem is that after Googling __traceback__ for a while I found only few articles but none of them describes whether or why you should (not) use __traceback__.

However, the Python 3 documentation for raise says that:

A traceback object is normally created automatically when an exception is raised and attached to it as the __traceback__ attribute, which is writable.

So I assume it's meant to be used.

psmears
  • 26,070
  • 4
  • 40
  • 48
Vyktor
  • 20,559
  • 6
  • 64
  • 96
  • 5
    Yes, it is meant to be used. From [What’s New In Python 3.0](https://docs.python.org/3.5/whatsnew/3.0.html) "PEP 3134: Exception objects now store their traceback as the __traceback__ attribute. This means that an exception object now contains all the information pertaining to an exception, and there are fewer reasons to use sys.exc_info() (though the latter is not removed)." – Maciej Szpakowski Dec 23 '15 at 01:18
  • I don't really understand why this answer is so hesitant and equivocal. It's a documented property; why would it *not* be "meant to be used"? – Mark Amery Jul 02 '17 at 19:00
  • 3
    @MarkAmery Possibly the `__` in the name indicating that it's an implementation detail, not a public property? – Basic Aug 01 '17 at 13:15
  • 13
    @Basic that's not what it indicates here. Conventionally in Python `__foo` is a private method but `__foo__` (with trailing underscores too) is a "magic" method (and not private). – Mark Amery Aug 01 '17 at 15:53
  • 2
    FYI, the `__traceback__` attribute is 100% safe to use however you like, with no GC implications. It's hard to tell that from the documentation, but ecatmur found [hard evidence](https://stackoverflow.com/questions/11414894/extract-traceback-info-from-an-exception-object/11415140#comment19173332_11417308). – senderle Dec 18 '17 at 15:08
  • You definitely should not store the `__traceback__` object or use it outside of the except clause since it won't be garbage collected. As this contains all the stack frames and will keep increasing in size. If you wish store it as a string. But dont store / use the object outside the except. – Mark May 07 '21 at 21:03
  • for more info why pls see the caution on the of https://stackoverflow.com/a/67441882/5506988 – Mark May 07 '21 at 21:33
85

A way to get traceback as a string from an exception object in Python 3:

import traceback

# `e` is an exception object that you get from somewhere
traceback_str = ''.join(traceback.format_tb(e.__traceback__))

traceback.format_tb(...) returns a list of strings. ''.join(...) joins them together.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Hieu
  • 7,138
  • 2
  • 42
  • 34
49

As an aside, if you want to actually get the full traceback as you would see it printed to your terminal, you want this:

>>> try:
...     print(1/0)
... except Exception as e:
...     exc = e
...
>>> exc
ZeroDivisionError('division by zero')
>>> tb_str = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
>>> tb_str
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
>>> print("".join(tb_str))
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

If you use format_tb as above answers suggest you'll get less information:

>>> tb_str = "".join(traceback.format_tb(exc.__traceback__))
>>> print("".join(tb_str))
  File "<stdin>", line 2, in <module>
Daniel Porteous
  • 5,536
  • 3
  • 25
  • 44
  • 4
    Finally! This should be the top answer. Thank you, Daniel! – Dany May 17 '19 at 11:17
  • 3
    Argh, I had spent the last 20 minutes trying to figure this out before I found this :-) `etype=type(exc)` can be omitted now btw: "Changed in version 3.5: The etype argument is ignored and inferred from the type of value." https://docs.python.org/3.7/library/traceback.html#traceback.print_exception Tested in Python 3.7.3. – Ciro Santilli OurBigBook.com May 18 '19 at 13:08
  • please don't store `exc` pls see the warning at the end of my answer https://stackoverflow.com/a/67441882/5506988 – Mark May 07 '21 at 21:32
  • In Python 3.10, the `etype` argument doesn't exist, but apparently `traceback.format_exception(exc)` seems to do the trick. Or if you only want to print it: `traceback.print_exception(exc)`. – PieterNuyts Oct 20 '22 at 13:13
10

You could use traceback.format_exc which returns a str

or traceback.print_exc prints to stdout

import traceback

try:
    b"x81".decode()
except UnicodeError:
    traceback.print_exc() # prints to stdout
    my_traceback = traceback.format_exc() # returns a str
print(my_traceback)

If you need to get it from the actual exception (although I don't see why)

traceback.format_exception returns a str

traceback.print_exception prints to stdout

import traceback

try:
    b"x81".decode()
except UnicodeError as exc:
    # etype is inferred from `value` since python3.5 so no need to pass a value...
    # format_exception returns a list
    my_traceback = "".join(traceback.format_exception(etype=None, value=exc, tb=exc.__traceback__))
    traceback.print_exception(etype=None, value=exc, tb=exc.__traceback__)

Caution

Do not store a reference to __traceback__ (or exc) for later use because the traceback object contains references to all the stack frame objects, which comprise the call stack, and each stack frame contains references to all of its local variables. As such, the size of the transitive closure of objects reachable from the traceback object can be very large. And if you maintain that reference, these objects will not be garbage‑collected. Prefer to render tracebacks into another form for even short‑term storage in memory."

Robert Smallshire - Python Beyond the Basics - 11 - Exceptions and Errors - Traceback objects

Mark
  • 1,337
  • 23
  • 34
  • Regarding the caution of storing the `__traceback__` or `exc`, doesn't this problem go away as soon as `__traceback__` goes out of scope? It will be destroyed and won't have a reference to the stack frame/local variables, which will then get destroyed. I would think that as long as you didn't store it in a global container this won't be a problem. – Matthew Moisen Aug 24 '22 at 17:02
9

There's a very good reason the traceback is not stored in the exception; because the traceback holds references to its stack's locals, this would result in a circular reference and (temporary) memory leak until the circular GC kicks in. (This is why you should never store the traceback in a local variable.)

About the only thing I can think of would be for you to monkeypatch stuff's globals so that when it thinks it's catching Exception it's actually catching a specialised type and the exception propagates to you as the caller:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()
mit
  • 11,083
  • 11
  • 50
  • 74
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 8
    This is wrong. Python 3 does put the traceback object in the exception, as `e.__traceback__`. – Glenn Maynard Dec 15 '12 at 16:44
  • 7
    @GlennMaynard Python 3 resolves the issue by deleting the exception target on exiting the `except` block, per [PEP 3110](http://www.python.org/dev/peps/pep-3110/#semantic-changes). – ecatmur Dec 17 '12 at 09:51