关于python:如何在不停止程序的情况下打印完整的traceback ?

How to print the full traceback without halting the program?

我正在编写一个程序,它解析10个网站,定位数据文件,保存文件,然后解析它们以生成可以在numpy库中随时使用的数据。这个文件通过坏链接、格式不好的XML、缺少条目和其他我还没有分类的东西遇到了大量的错误。我最初制作这个程序是为了处理这样的错误:

1
2
3
4
try:
    do_stuff()
except:
    pass

但现在我想记录错误:

1
2
3
4
try:
    do_stuff()
except Exception, err:
    print Exception, err

注意:这是打印到日志文件以供以后查看。这通常会打印非常无用的数据。我想要的是在不进行尝试的情况下打印错误触发时打印的完全相同的行,除了截取异常,但我不希望它停止我的程序,因为它嵌套在一系列的for循环中,我希望看到它完成。


如果这是你想要的,traceback.format_exc()sys.exc_info()将产生更多的信息。

1
2
3
4
5
6
7
8
9
import traceback
import sys

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    # or
    print(sys.exc_info()[0])

其他一些答案已经指出了回溯模块。

请注意,使用print_exc,在某些角落情况下,您将无法获得预期的结果。在Python 2。

1
2
3
4
5
6
7
8
9
10
11
import traceback

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_exc()

…将显示上一个异常的回溯:

1
2
3
4
Traceback (most recent call last):
  File"e.py", line 7, in <module>
    raise TypeError("Again !?!")
TypeError: Again !?!

如果您真的需要访问原始的回溯,一个解决方案是将从exc_info返回的异常信息缓存在局部变量中,并使用print_exception显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import traceback
import sys

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        exc_info = sys.exc_info()

        # do you usefull stuff here
        # (potentially raising an exception)
        try:
            raise TypeError("Again !?!")
        except:
            pass
        # end of useful stuff


    finally:
        # Display the *original* exception
        traceback.print_exception(*exc_info)
        del exc_info

生产:

1
2
3
4
Traceback (most recent call last):
  File"t.py", line 6, in <module>
    raise TypeError("Oups!")
TypeError: Oups!

不过,很少有这样的陷阱:

  • 来自sys_info号文件:


    Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference. This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected. [...] If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement)

  • 但是,来自同一个文件:


    Beginning with Python 2.2, such cycles are automatically reclaimed when garbage collection is enabled and they become unreachable, but it remains more efficient to avoid creating cycles.

另一方面,通过允许您访问与异常关联的回溯,python 3产生了一个不那么令人惊讶的结果:

1
2
3
4
5
6
7
8
9
10
11
import traceback

try:
    raise TypeError("Oups!")
except Exception as err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_tb(err.__traceback__)

…将显示:

1
2
  File"e3.py", line 4, in <module>
    raise TypeError("Oups!")


如果正在调试并且只想查看当前堆栈跟踪,则可以简单地调用:

traceback.print_stack()

不需要手动引发异常以再次捕获它。


How to print the full traceback without halting the program?

如果您不想在出现错误时停止程序,则需要尝试/排除以下情况来处理该错误:

1
2
3
4
try:
    do_something_that_might_error()
except Exception as error:
    handle_the_error(error)

为了提取完整的回溯,我们将使用标准库中的traceback模块:

1
import traceback

为了创建一个相当复杂的stacktrace来证明我们得到了完整的stacktrace:

1
2
3
4
5
def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

印刷

要打印完整的回溯,请使用traceback.print_exc方法:

1
2
3
4
try:
    do_something_that_might_error()
except Exception as error:
    traceback.print_exc()

哪些印刷品:

1
2
3
4
5
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!

优于打印、日志记录:

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

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

在这种情况下,您需要使用logger.exception函数来代替:

1
2
3
4
try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

哪些日志:

1
2
3
4
5
6
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!

或者您只需要字符串,在这种情况下,您将需要traceback.format_exc函数:

1
2
3
4
try:
    do_something_that_might_error()
except Exception as error:
    logger.debug(traceback.format_exc())

哪些日志:

1
2
3
4
5
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!

结论

对于这三个选项,我们看到我们得到的输出与出现错误时相同:

1
2
3
4
5
6
>>> 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!


除了@aron hall的答案外,如果您正在登录,但不想使用logging.exception()(因为它在错误级别登录),您可以使用较低级别并通过exc_info=True。例如

1
2
3
4
try:
    do_something_that_might_error()
except Exception:
    logger.info('General exception noted.', exc_info=True)

要获取精确的堆栈跟踪(作为字符串),如果没有try/except可以跳过它,则会引发该跟踪,只需将其放在捕获异常的except块中。

1
desired_trace = traceback.format_exc(sys.exc_info())

以下是如何使用它(假设定义了flaky_func,并且log调用您最喜欢的日志系统):

1
2
3
4
5
6
7
8
9
10
import traceback
import sys

try:
    flaky_func()
except KeyboardInterrupt:
    raise
except Exception:
    desired_trace = traceback.format_exc(sys.exc_info())
    log(desired_trace)

捕获并重新提升KeyboardInterrupt是一个好主意,这样您仍然可以使用ctrl-c终止程序。日志记录不在问题的范围内,但日志记录是一个不错的选择。系统和追溯模块的文档。


您需要将try/except放在可能发生错误的最内部的loop中,即

1
2
3
4
5
6
7
8
9
10
11
for i in something:
    for j in somethingelse:
        for k in whatever:
            try:
                something_complex(i, j, k)
            except Exception, e:
                print e
        try:
            something_less_complex(i, j)
        except Exception, e:
            print e

…等等

换言之,您需要将可能在try/except中失败的语句包装在尽可能多的内部循环中,尽可能具体。


首先,不要使用print进行日志记录,有一个稳定的、经过验证的、经过深思熟虑的stdlib模块:logging。你绝对应该用它来代替。

第二,当存在本机的简单方法时,不要被诱惑去使用不相关的工具。这里是:

1
2
3
4
5
6
log = logging.getLogger(__name__)

try:
    call_code_that_fails()
except MyError:
    log.exception('Any extra info you want to see in your logs')

就是这样。你现在完了。

对事情如何发展感兴趣的人的解释

log.exception所做的实际上只是调用log.error(即用ERROR级记录事件),然后打印回溯。

为什么更好?

好吧,这里有一些考虑:

  • 这是正确的;
  • 这是直截了当的;
  • 这很简单。

为什么没有人不应该使用treceback,不应该用exc_info=True调用logger,也不应该用sys.exc_info弄脏他/她的手?

嗯,因为!它们都是为了不同的目的而存在的。例如,traceback.print_exc的输出与解释器本身产生的回溯略有不同。如果你要用它,你会把任何人用你的日志敲他的头弄混。

exc_info=True传给日志调用是不适当的。但是,当您捕获可恢复的错误并希望将它们记录(使用,例如INFO级)并进行回溯时,它是有用的,因为log.exception只生成一个级别的日志—ERROR

你一定要尽量避免和sys.exc_info搞混。它不是公共接口,而是内部接口——如果你确实知道自己在做什么,你可以使用它。它不仅仅用于打印异常。


关于这个答案的评论:print(traceback.format_exc())对我来说比traceback.print_exc()更好。对于后者,hello有时会与回溯文本奇怪地"混合",例如,如果两者都想同时写入stdout或stderr,则会产生奇怪的输出(至少在从文本编辑器内部构建并在"构建结果"面板中查看输出时)。

Traceback (most recent call last):
File"C:\Users\User\Desktop\test.py", line 7, in
hell do_stuff()
File"C:\Users\User\Desktop\test.py", line 4, in do_stuff
1/0
ZeroDivisionError: integer division or modulo by zero
o
[Finished in 0.1s]

所以我用:

1
2
3
4
5
6
7
8
9
10
import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')

您需要跟踪模块。它将允许您像Python一样打印堆栈转储。特别是,print_last函数将打印最后一个异常和堆栈跟踪。