无需暂停/退出程序即可捕获并打印完整的 Python 异常回溯
- 2024-12-20 08:37:00
- admin 原创
- 90
问题描述:
我想捕获并记录异常而不退出,例如,
try:
do_stuff()
except Exception as err:
print(Exception, err)
# I want to print the entire traceback here,
# not just the exception name and details
我想打印与引发异常时打印的完全相同的输出,而无需 try/except 拦截异常,并且我不希望它退出我的程序。
解决方案 1:
traceback.format_exc()
如果这是您想要的,将会提供更多信息。
import traceback
def do_stuff():
raise Exception("test exception")
try:
do_stuff()
except Exception:
print(traceback.format_exc())
输出:
Traceback (most recent call last):
File "main.py", line 9, in <module>
do_stuff()
File "main.py", line 5, in do_stuff
raise Exception("test exception")
Exception: test exception
解决方案 2:
其他一些答案已经指出了回溯模块。
请注意,使用print_exc
,在某些情况下,您将无法获得预期的结果。在 Python 2.x 中:
import traceback
try:
raise TypeError("Oups!")
except Exception, err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_exc()
...将显示最后一个异常的回溯:
Traceback (most recent call last):
File "e.py", line 7, in <module>
raise TypeError("Again !?!")
TypeError: Again !?!
如果您确实需要访问原始回溯,一种解决方案是缓存局部变量中返回的异常信息并使用以下命令显示它:exc_info
`print_exception`
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
制作:
Traceback (most recent call last):
File "t.py", line 6, in <module>
raise TypeError("Oups!")
TypeError: Oups!
不过,这样做也存在一些缺陷:
来自文档
sys_info
:
将回溯返回值分配给处理异常的函数中的局部变量将导致循环引用。这将阻止同一函数中的局部变量或回溯引用的任何内容被垃圾收集。[...]如果您确实需要回溯,请确保在使用后将其删除(最好使用 try ... finally 语句完成)
但是,从同一篇文档来看:
从 Python 2.2 开始,当启用垃圾收集并且它们变得无法访问时,这些循环会被自动回收,但避免创建循环仍然更有效。
另一方面,通过允许您访问与异常相关的回溯,Python 3 产生了一个不那么令人惊讶的结果:
import traceback
try:
raise TypeError("Oups!")
except Exception as err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_tb(err.__traceback__)
...将显示:
File "e3.py", line 4, in <module>
raise TypeError("Oups!")
解决方案 3:
如果您正在调试并且只想查看当前堆栈跟踪,您可以简单地调用:
traceback.print_stack()
没有必要手动引发异常只是为了再次捕获它。
解决方案 4:
如何在不停止程序的情况下打印完整的回溯?
当你不想因为错误而停止程序时,你需要使用 try/except 来处理该错误:
try:
do_something_that_might_error()
except Exception as error:
handle_the_error(error)
为了提取完整的回溯,我们将使用traceback
标准库中的模块:
import traceback
并创建一个相当复杂的堆栈跟踪来证明我们获得了完整的堆栈跟踪:
def raise_error():
raise RuntimeError('something bad happened!')
def do_something_that_might_error():
raise_error()
印刷
要打印完整的回溯,请使用以下traceback.print_exc
命令:
try:
do_something_that_might_error()
except Exception as error:
traceback.print_exc()
打印内容:
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!
比打印、记录更好:
但是,最佳做法是为模块设置一个记录器。它将知道模块的名称并能够更改级别(以及其他属性,例如处理程序)
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
在这种情况下,您需要使用logger.exception
以下函数:
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!
或者也许您只想要字符串,在这种情况下,您将需要traceback.format_exc
函数:
try:
do_something_that_might_error()
except Exception as error:
logger.debug(traceback.format_exc())
记录如下:
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!
结论
对于所有三个选项,我们看到得到的输出与出现错误时相同:
>>> 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!
使用哪个
性能问题在这里并不重要,因为 IO 通常占主导地位。我更喜欢,因为它以向前兼容的方式精确地完成了所请求的工作:
logger.exception(error)
可以调整日志级别和输出,无需修改代码即可轻松关闭。通常,执行直接需要的操作是最有效的方法。
解决方案 5:
traceback.format_exception(exception_object)
如果您只有异常对象,那么您可以使用以下命令从 Python 3 中代码的任何一点以字符串形式获取回溯:
import traceback
''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
从 Python 3.10 开始,这可以进一步简化为:
tb_str = ''.join(traceback.format_exception(exc_obj))
完整示例:
#!/usr/bin/env python3
import traceback
def f():
g()
def g():
raise Exception('asdf')
try:
g()
except Exception as e:
exc_obj = e
tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)
或者使用更简单的 Python 3.10 界面:
tb_str = ''.join(traceback.format_exception(exc_obj))
其中任一个的输出:
Traceback (most recent call last):
File "./main.py", line 12, in <module>
g()
File "./main.py", line 9, in g
raise Exception('asdf')
Exception: asdf
文档:https ://docs.python.org/3.10/library/traceback.html#traceback.format_exception
另请参阅:从异常对象中提取回溯信息
测试了Python 3.11.4、Ubuntu 23.04。
解决方案 6:
首先,不要使用print
s 进行日志记录,有一个稳定、经过验证且经过深思熟虑的stdlib
模块可以做到这一点:logging
。你绝对应该使用它。
其次,当有简单易用的方法时,不要试图用不相关的工具来做乱七八糟的事情。方法如下:
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
)然后打印回溯。
为什么它更好?
嗯,这里有一些注意事项:
正好;
它很简单;
它很简单。
为什么没人应该使用traceback
或调用记录器exc_info=True
或者亲自动手呢sys.exc_info
?
嗯,只是因为!它们都是为了不同的目的而存在的。例如,traceback.print_exc
的输出与解释器本身生成的回溯略有不同。如果你使用它,你会让任何阅读你的日志的人感到困惑,他们会为之绞尽脑汁。
传递exc_info=True
给日志调用是不合适的。但是,当捕获可恢复错误并且您希望使用INFO
回溯记录它们(例如使用级别)时,它很有用,因为log.exception
只生成一个级别的日志 - ERROR
。
而且你绝对应该尽量避免弄乱sys.exc_info
。它不是一个公共接口,而是一个内部接口 - 如果你确实知道自己在做什么,就可以使用它。它不只是用于打印异常。
解决方案 7:
除了Aaron Hall 的回答之外,如果您正在记录,但不想使用logging.exception()
(因为它以 ERROR 级别记录),您可以使用较低的级别并传递exc_info=True
。例如
try:
do_something_that_might_error()
except Exception:
logging.info('General exception noted.', exc_info=True)
解决方案 8:
我没有在其他任何答案中看到这一点。如果您出于某种原因传递 Exception 对象...
在 Python 3.5+ 中,你可以使用traceback.TracebackException.from_exception()从 Exception 对象获取跟踪。例如:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
try:
stack_lvl_3()
except Exception as e:
# raise
return e
def stack_lvl_1():
e = stack_lvl_2()
return e
e = stack_lvl_1()
tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))
然而,上面的代码导致:
Traceback (most recent call last):
File "exc.py", line 10, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')
stack_lvl_2()
这只是堆栈的两个级别,而不是在引发异常但未被拦截(取消注释该行)时在屏幕上打印的内容# raise
。
据我了解,这是因为异常在引发时仅记录堆栈的当前级别(stack_lvl_3()
在本例中)。当它通过堆栈向上传递时,更多级别被添加到其 中__traceback__
。但我们在 中拦截了它stack_lvl_2()
,这意味着它只能记录级别 3 和 2。要获得在 stdout 上打印的完整跟踪,我们必须在最高(最低?)级别捕获它:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
stack_lvl_3()
def stack_lvl_1():
stack_lvl_2()
try:
stack_lvl_1()
except Exception as exc:
tb = traceback.TracebackException.from_exception(exc)
print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))
其结果是:
Handled at stack lvl 0
File "exc.py", line 17, in <module>
stack_lvl_1()
File "exc.py", line 13, in stack_lvl_1
stack_lvl_2()
File "exc.py", line 9, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
请注意,堆栈打印不同,缺少第一行和最后一行。因为它是不同的format()
。
尽可能远离异常发生点拦截异常可以使代码更简单,同时提供更多信息。
解决方案 9:
在 python3(适用于 3.9)中,我们可以定义一个函数并在任何我们想要打印详细信息的地方使用它。
import traceback
def get_traceback(e):
lines = traceback.format_exception(type(e), e, e.__traceback__)
return ''.join(lines)
try:
1/0
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
try:
spam(1,2)
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
输出结果如下:
bash-3.2$ python3 /Users/soumyabratakole/PycharmProjects/pythonProject/main.py
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 26, in <module>
1/0
ZeroDivisionError: division by zero
------End--------
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 33, in <module>
spam(1,2)
NameError: name 'spam' is not defined
------End--------
解决方案 10:
如果您已经有一个 Error 对象,并且想要打印整个内容,则需要进行这个略显尴尬的调用:
import traceback
traceback.print_exception(type(err), err, err.__traceback__)
没错,它print_exception
采用三个位置参数:异常的类型、实际异常对象和异常自身的内部回溯属性。
在 python 3.5 或更高版本中,type(err)
是可选的...但它是一个位置参数,因此您仍然必须在其位置明确传递 None。
traceback.print_exception(None, err, err.__traceback__)
我不知道为什么这一切不只是traceback.print_exception(err)
。为什么你会想要打印出一个错误,以及一个不属于该错误的回溯,这让我无法理解。
解决方案 11:
import io
import traceback
try:
call_code_that_fails()
except:
errors = io.StringIO()
traceback.print_exc(file=errors) # Instead of printing directly to stdout, the result can be further processed
contents = str(errors.getvalue())
print(contents)
errors.close()
解决方案 12:
要获得精确的堆栈跟踪(以字符串形式),如果没有 try/except 跨越它就会引发该跟踪,只需将其放在捕获有问题的异常的 except 块中即可。
desired_trace = traceback.format_exc(sys.exc_info())
以下是如何使用它(假设flaky_func
已定义,并log
调用您最喜欢的日志系统):
import traceback
import sys
try:
flaky_func()
except KeyboardInterrupt:
raise
except Exception:
desired_trace = traceback.format_exc(sys.exc_info())
log(desired_trace)
捕获并重新引发KeyboardInterrupt
s 是一个好主意,这样您仍然可以使用 Ctrl-C 终止程序。日志记录超出了问题的范围,但一个不错的选择是日志记录。sys和traceback模块的文档。
解决方案 13:
您需要将 try/except 放在可能发生错误的最内层循环内,即
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 中失败的语句,并尽可能将其包装在最内部的循环中。
解决方案 14:
关于这个答案的评论的评论:print(traceback.format_exc())
对我来说比 做得更好traceback.print_exc()
。对于后者,hello
有时会奇怪地与回溯文本“混合”,就像如果两者都想同时写入 stdout 或 stderr 一样,会产生奇怪的输出(至少在从文本编辑器内部构建并在“构建结果”面板中查看输出时)。
回溯(最近一次调用最后一次):
文件“C:\Users\User\Desktop\test.py”,第 7 行,在
hell do_stuff()
文件“C:\Users\User\Desktop\test.py”,第 4 行,在 do_stuff
1/0
ZeroDivisionError:整数除法或以零取模
o
[0.1 秒内完成]
所以我使用:
import traceback, sys
def do_stuff():
1/0
try:
do_stuff()
except Exception:
print(traceback.format_exc())
print('hello')
解决方案 15:
此答案中的这种用法是自 Python 3.10 以来的新用法,之前的任何答案都没有涉及过。要打印回溯,可以将异常提供给traceback.print_exception
。
例子:
import traceback
try:
object.bad_attr
except Exception as exc:
traceback.print_exception(exc)
输出回溯:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: type object 'object' has no attribute 'bad_attr'
提醒一下,此答案需要 Python 3.10 或更新版本才能起作用。
要将引用捕获为字符串,请参阅此答案。
解决方案 16:
您需要traceback模块。它将允许您像 Python 通常所做的那样打印堆栈转储。特别是,print_last函数将打印最后一个异常和堆栈跟踪。
解决方案 17:
python3 解决方案
stacktrace_helper.py
:
from linecache import getline
import sys
import traceback
def get_stack_trace():
exc_type, exc_value, exc_tb = sys.exc_info()
trace = traceback.format_stack()
trace = list(filter(lambda x: ("\\lib\\\" not in x and "/lib/" not in x and "stacktrace_helper.py" not in x), trace))
ex_type = exc_type.__name__
ex_line = exc_tb.tb_lineno
ex_file = exc_tb.tb_frame.f_code.co_filename
ex_message = str(exc_value)
line_code = ""
try:
line_code = getline(ex_file, ex_line).strip()
except:
pass
trace.insert(
0, f'File "{ex_file}", line {ex_line}, line_code: {line_code} , ex: {ex_type} {ex_message}',
)
return trace
def get_stack_trace_str(msg: str = ""):
trace = list(get_stack_trace())
trace_str = "
".join(list(map(str, trace)))
trace_str = msg + "
" + trace_str
return trace_str
解决方案 18:
traceback
我们无需使用模块.error()
或.exception()
日志记录方法就可以记录异常回溯。
import logging
try:
print(0/0)
except Exception as e:
logging.error(e, exc_info=True)
或者
import logging
try:
print(0/0)
except Exception as e:
logging.exception(e)
解决方案 19:
这是我的解决方案,将错误写入日志文件和控制台中:
import logging, sys
import traceback
logging.basicConfig(filename='error.log', level=logging.DEBUG)
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
exc_info=(exc_type, exc_value, exc_traceback)
logging.critical("
Date:" + str(datetime.datetime.now()), exc_info=(exc_type, exc_value, exc_traceback))
print("An error occured, check error.log to see the error details")
traceback.print_exception(*exc_info)
sys.excepthook = handle_exception
解决方案 20:
你可以这样做:
try:
do_stuff()
except Exception, err:
print(Exception, err)
raise err
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件