【问题标题】:Get exception description and stack trace which caused an exception, all as a string获取导致异常的异常描述和堆栈跟踪,全部作为字符串
【发布时间】:2011-06-01 15:54:19
【问题描述】:

我看过很多关于 Python 中的堆栈跟踪和异常的帖子。但是还没有找到我需要的东西。

我有一大段 Python 2.7 代码可​​能会引发异常。我想抓住它并将它的完整描述和导致错误的堆栈跟踪分配给一个 string(只是我们在控制台上看到的所有内容)。我需要这个字符串将其打印到 GUI 中的文本框中。

类似这样的:

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

问题是:complete_exception_description的功能是什么?

【问题讨论】:

    标签: python python-2.7 exception-handling stack-trace


    【解决方案1】:

    参见traceback 模块,特别是format_exc() 函数。 Here.

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

    【讨论】:

    • 这仅适用于最后一个错误吗?如果您开始将错误传递给其他代码位会发生什么?我正在写一个log_error(err) 函数。
    • 它适用于捕获和处理的错误。
    • 除了我一直在寻找的内容之外,我刚刚了解到try 可以采用else 子句。
    【解决方案2】:

    让我们创建一个相当复杂的堆栈跟踪,以证明我们获得了完整的堆栈跟踪:

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

    记录完整的堆栈跟踪

    最佳做法是为您的模块设置一个记录器。它将知道模块的名称并能够更改级别(以及其他属性,例如处理程序)

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

    我们可以使用这个记录器来获取错误:

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

    哪些日志:

    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!
    

    所以我们得到的输出与出现错误时相同:

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

    只获取字符串

    如果您真的只想要字符串,请改用traceback.format_exc 函数,在此处演示如何记录字符串:

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

    哪些日志:

    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!
    

    【讨论】:

    • 这是使用 python 3 时最好的方法(与下面的一些答案相比)?
    • @Yunti 我相信这个 API 在 Python 2 和 3 中是一致的。
    • 这个答案的格式在meta上讨论过:meta.stackoverflow.com/questions/386477/…
    • 我向以下内容发送了编辑但未登录,因此显示为匿名:except Exception as e: logger.exception("&lt;&lt;clearly and distinctly describe what failed here&gt;&gt;", exc_info=e)
    • @arntg 感谢您尝试提供帮助,但该编辑将是有害的更改。以后请更加小心,以充分了解您尝试使用的 API。在这种情况下,exc_info 参数需要一个“异常元组”,而errorException 对象(或子类)的实例,无需将error 更改为e
    【解决方案3】:

    使用 Python 3,以下代码将格式化 Exception 对象,与使用 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__)))
    

    优点是只需要Exception 对象(感谢记录的__traceback__ 属性),因此可以更容易地作为参数传递给另一个函数以进行进一步处理。

    【讨论】:

    【解决方案4】:
    >>> 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
    

    您使用sys.exc_info() 来收集traceback 模块中的信息和函数来格式化它。 Here 是一些格式化它的例子。

    整个异常字符串位于:

    >>> 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']
    

    【讨论】:

      【解决方案5】:

      对于Python 3.5+

      因此,您可以像从任何其他异常中一样从异常中获取堆栈跟踪。使用traceback.TracebackException (只需将ex 替换为您的例外):

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

      执行此操作的扩展示例和其他功能:

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

      输出将是这样的:

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

      【讨论】:

        【解决方案6】:

        对于那些使用 Python-3

        的人

        使用traceback 模块和exception.__traceback__ 可以提取堆栈跟踪如下:

        • 使用traceback.extract_stack()获取当前堆栈跟踪
        • 删除最后三个元素(因为它们是堆栈中的条目,可让我使用调试功能)
        • 使用traceback.extract_tb()从异常对象追加__traceback__
        • 使用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)
        

        一个简单的演示:

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

        当我们调用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
        

        【讨论】:

        【解决方案7】:

        如果您想在未处理异常时获得相同的信息,您可以执行以下操作。做import traceback 然后:

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

        我使用的是 Python 3.7。

        【讨论】:

        • 好答案!小备注: traceback.print_tb() 已经打印。所以外部 print() 返回 None 并且输出是堆栈跟踪,然后是“None”。 print() 不是没用的
        【解决方案8】:

        您也可以考虑使用内置 Python 模块 cgitb 来获取一些非常好的、格式良好的异常信息,包括局部变量值、源代码上下文、函数参数等。

        例如这段代码...

        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)
        

        我们得到这个异常输出...

        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
        

        【讨论】:

        • 感谢我提出了一个我什至不知道标准库中存在的模块。
        【解决方案9】:

        如果您的目标是让异常和堆栈跟踪消息看起来与 python 抛出错误时完全一样,那么以下在 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()
        

        它的工作原理是从堆栈中删除最后一个 format_stacktrace() 调用并加入其余调用。运行时,上面的示例给出以下输出:

        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.
        

        【讨论】:

          【解决方案10】:

          我的 2 美分:

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

          【讨论】:

            【解决方案11】:

            我定义了以下帮助类:

            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
            

            我以后可以这样使用:

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

            以后可以这样消费:

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

            (背景:我很沮丧,因为将Promises 与Exceptions 一起使用,不幸的是,它会将一个地方引发的异常传递给另一个地方的 on_rejected 处理程序,因此很难从原始获取回溯位置)

            【讨论】:

              【解决方案12】:

              如果您想将回溯转换为 dict 列表(对于 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
              

              这将是(一个示例)结果:

              [
                 {
                    "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"
                 }
              ]
              

              【讨论】:

                猜你喜欢
                • 2010-11-20
                • 1970-01-01
                • 2011-01-05
                • 2018-07-19
                • 2023-03-22
                • 1970-01-01
                • 2017-08-06
                • 2010-09-13
                相关资源
                最近更新 更多