ChatGPT解决这个技术问题 Extra ChatGPT

Get exception description and stack trace which caused an exception, all as a string

I've seen a lot of posts about stack trace and exceptions in Python. But haven't found what I need.

I have a chunk of Python 2.7 code that may raise an exception. I would like to catch it and assign to a string its full description and the stack trace that caused the error (simply all we use to see on the console). I need this string to print it to a text box in the GUI.

Something like this:

try:
    method_that_can_raise_an_exception(params)
except Exception as e:
    print_to_textbox(complete_exception_description(e))

The problem is: what is the function complete_exception_description?


i
ianace

See the traceback module, specifically the format_exc() function. Here.

import traceback

try:
    raise ValueError
except ValueError:
    tb = traceback.format_exc()
else:
    tb = "No error"
finally:
    print tb

Does this only work with the last error? What happens if you start passing the error around to other bits of code? I'm writing a log_error(err) function.
It works with the error that was caught and handled.
In addition to what I was looking for, I just learned that try can take an else clause.
Can you show the output in case of an exception?
L
Lundin

Let's create a decently complicated stacktrace, in order to demonstrate that we get the full stacktrace:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

Logging the full stacktrace

A best practice is to have a logger set up for your module. It will know the name of the module and be able to change levels (among other attributes, such as handlers)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

And we can use this logger to get the error:

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Which logs:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

And so we get the same output as when we have an error:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Getting just the string

If you really just want the string, use the traceback.format_exc function instead, demonstrating logging the string here:

import traceback
try:
    do_something_that_might_error()
except Exception as error:
    just_the_string = traceback.format_exc()
    logger.debug(just_the_string)

Which logs:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

is this the best method when using python 3 (compared to eg some of the answers below)?
@Yunti I believe this API has been consistent across Python 2 and 3.
Formatting of this answer was discussed on meta: meta.stackoverflow.com/questions/386477/….
I sent an edit to the following but was not logged in so showing as anonymous: except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
@arntg I appreciate that you're trying to help, but that edit would be a harmful change. Please be much more careful in the future to fully understand the APIs you are attempting to use. In this case, the exc_info argument expects an "exception tuple" whereas the error is an instance of the Exception object (or subclass), and there is no need to change error to e.
C
Community

With Python 3, the following code will format an Exception object exactly as would be obtained using traceback.format_exc():

import traceback

try: 
    method_that_can_raise_an_exception(params)
except Exception as ex:
    print(''.join(traceback.format_exception(etype=type(ex), value=ex, tb=ex.__traceback__)))

The advantage being that only the Exception object is needed (thanks to the recorded __traceback__ attribute), and can therefore be more easily passed as an argument to another function for further processing.


It is better than sys.exc_info() which is not good OO style and use global variable.
This asks specifically how to get the traceback from the exception object as you have done here: stackoverflow.com/questions/11414894/…
There is a simpler Python3 way without using .__traceback__ and type, see stackoverflow.com/a/58764987/5717886
A
Asclepius
>>> import sys
>>> import traceback
>>> try:
...   5 / 0
... except ZeroDivisionError as e:
...   type_, value_, traceback_ = sys.exc_info()
>>> traceback.format_tb(traceback_)
['  File "<stdin>", line 2, in <module>\n']
>>> value_
ZeroDivisionError('integer division or modulo by zero',)
>>> type_
<type 'exceptions.ZeroDivisionError'>
>>>
>>> 5 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

You use sys.exc_info() to collect the information and the functions in the traceback module to format it. Here are some examples for formatting it.

The whole exception string is at:

>>> ex = traceback.format_exception(type_, value_, traceback_)
>>> ex
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: integer division or modulo by zero\n']

d
don_vanchos

For Python 3.5+:

So, you can get the stacktrace from your exception as from any other exception. Use traceback.TracebackException for it (just replace ex with your exception):

print("".join(traceback.TracebackException.from_exception(ex).format())

An extended example and other features to do this:

import traceback

try:
    1/0
except Exception as ex:
    print("".join(traceback.TracebackException.from_exception(ex).format()) == traceback.format_exc() == "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))) # This is True !!
    print("".join(traceback.TracebackException.from_exception(ex).format()))

The output will be something like this:

True
Traceback (most recent call last):
  File "untidsfsdfsdftled.py", line 29, in <module>
    1/0
ZeroDivisionError: division by zero

Brilliant. In 2022, this is effectively the accepted answer. All other answers are either substantially less Pythonic, violate privacy encapsulation, or operate only on the currently raised exception and thus fail to generate tracebacks for arbitrary exception objects. @don_vanchos: this may be all you've ever done and will ever do on StackOverflow – but it's enough. Oh, yes. It's enough.
M
Mike N

For those using Python-3

Using traceback module and exception.__traceback__ one can extract the stack-trace as follows:

grab the current stack-trace using traceback.extract_stack()

remove the last three elements (as those are entries in the stack that got me to my debug function)

append the __traceback__ from the exception object using traceback.extract_tb()

format the whole thing using traceback.format_list()

import traceback
def exception_to_string(excp):
   stack = traceback.extract_stack()[:-3] + traceback.extract_tb(excp.__traceback__)  # add limit=?? 
   pretty = traceback.format_list(stack)
   return ''.join(pretty) + '\n  {} {}'.format(excp.__class__,excp)

A simple demonstration:

def foo():
    try:
        something_invalid()
    except Exception as e:
        print(exception_to_string(e))

def bar():
    return foo()

We get the following output when we call bar():

  File "./test.py", line 57, in <module>
    bar()
  File "./test.py", line 55, in bar
    return foo()
  File "./test.py", line 50, in foo
    something_invalid()

  <class 'NameError'> name 'something_invalid' is not defined

It looks like an unreadable complicated code. In Python 3.5+ there is a more elegant and simple way: stackoverflow.com/a/58764987/5717886
b
bluish

You might also consider using the built-in Python module, cgitb, to get some really good, nicely formatted exception information including local variable values, source code context, function parameters etc..

For instance for this code...

import cgitb
cgitb.enable(format='text')

def func2(a, divisor):
    return a / divisor

def func1(a, b):
    c = b - 5
    return func2(a, c)

func1(1, 5)

we get this exception output...

ZeroDivisionError
Python 3.4.2: C:\tools\python\python.exe
Tue Sep 22 15:29:33 2015

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 c:\TEMP\cgittest2.py in <module>()
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
func1 = <function func1>

 c:\TEMP\cgittest2.py in func1(a=1, b=5)
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 c:\TEMP\cgittest2.py in func2(a=1, divisor=0)
    3
    4 def func2(a, divisor):
    5   return a / divisor
    6
    7 def func1(a, b):
a = 1
divisor = 0
ZeroDivisionError: division by zero
    __cause__ = None
    __class__ = <class 'ZeroDivisionError'>
    __context__ = None
    __delattr__ = <method-wrapper '__delattr__' of ZeroDivisionError object>
    __dict__ = {}
    __dir__ = <built-in method __dir__ of ZeroDivisionError object>
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __eq__ = <method-wrapper '__eq__' of ZeroDivisionError object>
    __format__ = <built-in method __format__ of ZeroDivisionError object>
    __ge__ = <method-wrapper '__ge__' of ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of ZeroDivisionError object>
    __gt__ = <method-wrapper '__gt__' of ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of ZeroDivisionError object>
    __le__ = <method-wrapper '__le__' of ZeroDivisionError object>
    __lt__ = <method-wrapper '__lt__' of ZeroDivisionError object>
    __ne__ = <method-wrapper '__ne__' of ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __suppress_context__ = False
    __traceback__ = <traceback object>
    args = ('division by zero',)
    with_traceback = <built-in method with_traceback of ZeroDivisionError object>

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgittest2.py", line 11, in <module>
    func1(1, 5)
  File "cgittest2.py", line 9, in func1
    return func2(a, c)
  File "cgittest2.py", line 5, in func2
    return a / divisor
ZeroDivisionError: division by zero

Kudos for coming up with a module I did not even know exists in the standard library.
P
Peter Mortensen

If you would like to get the same information given when an exception isn't handled you can do something like this. Do import traceback and then:

try:
    ...
except Exception as e:
    print(traceback.print_tb(e.__traceback__))

I'm using Python 3.7.


Good answer ! Little remark: traceback.print_tb() already prints. So the external print() returns None and the output is the stacktrace, followed by "None". print() is not useless
R
Rune Kaagaard

If your goal is to make the exception and stacktrace message look exactly like when python throws an error, the following works in both python 2+3:

import sys, traceback


def format_stacktrace():
    parts = ["Traceback (most recent call last):\n"]
    parts.extend(traceback.format_stack(limit=25)[:-2])
    parts.extend(traceback.format_exception(*sys.exc_info())[1:])
    return "".join(parts)

# EXAMPLE BELOW...

def a():
    b()


def b():
    c()


def c():
    d()


def d():
    assert False, "Noooh don't do it."


print("THIS IS THE FORMATTED STRING")
print("============================\n")

try:
    a()
except:
    stacktrace = format_stacktrace()
    print(stacktrace)

print("THIS IS HOW PYTHON DOES IT")
print("==========================\n")
a()

It works by removing the last format_stacktrace() call from the stack and joining the rest. When run, the example above gives the following output:

THIS IS THE FORMATTED STRING
============================

Traceback (most recent call last):
  File "test.py", line 31, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

THIS IS HOW PYTHON DOES IT
==========================

Traceback (most recent call last):
  File "test.py", line 38, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

2
2.7182818

my 2-cents:

import sys, traceback
try: 
  ...
except Exception, e:
  T, V, TB = sys.exc_info()
  print ''.join(traceback.format_exception(T,V,TB))

q
qbolec

I defined following helper class:

import traceback
class TracedExeptions(object):
    def __init__(self):
        pass
    def __enter__(self):
        pass

    def __exit__(self, etype, value, tb):
      if value :
        if not hasattr(value, 'traceString'):
          value.traceString = "\n".join(traceback.format_exception(etype, value, tb))
        return False
      return True

Which I can later use like this:

with TracedExeptions():
  #some-code-which-might-throw-any-exception

And later can consume it like this:

def log_err(ex):
  if hasattr(ex, 'traceString'):
    print("ERROR:{}".format(ex.traceString));
  else:
    print("ERROR:{}".format(ex));

(Background: I was frustraded because of using Promises together with Exceptions, which unfortunately passes exceptions raised in one place to a on_rejected handler in another place, and thus it is difficult to get the traceback from original location)


P
Pedro Nunes

If you would like to convert your traceback to a list of dict (for python > 3.5):

from traceback import TracebackException


def list_traceback(exc_value: BaseException):
    result = list()

    # get previous fails, so errors are appended by order of execution
    if exc_value.__context__:
        result += list_traceback(exc_value.__context__)

    # convert Exception into TracebackException
    tbe = TracebackException.from_exception(exc_value)

    # get stacktrace (cascade methods calls)
    error_lines = list()
    for frame_summary in tbe.stack:
        summary_details = {
            'filename': frame_summary.filename,
            'method'  : frame_summary.name,
            'lineno'  : frame_summary.lineno,
            'code'    : frame_summary.line
        }
        error_lines.append(summary_details)

    # append error, by order of execution
    result.append({"error_lines": error_lines,
                   "type"       : tbe.exc_type.__name__,
                   "message"    : str(tbe)})

    return result

This will be (an example of) the result:

[
   {
      "error_lines": [
         {
            "filename": "/home/demo/file2.py",
            "method": "do_error_2",
            "lineno": 18,
            "code": "a=1/0"
         }
      ],
      "type": "ZeroDivisionError",
      "message": "division by zero"
   },
   {
      "error_lines": [
         {
            "filename": "/home/demo/file_main.py",
            "method": "demo2",
            "lineno": 50,
            "code": "file2.DEMO().do_error_2()"
         },
         {
            "filename": "/home/demo/file2.py",
            "method": "do_error_2",
            "lineno": 20,
            "code": "raise AssertionError(\"Raised inside the except, after division by zero\")"
         }
      ],
      "type": "AssertionError",
      "message": "Raised inside the except, after division by zero"
   }
]