ChatGPT解决这个技术问题 Extra ChatGPT

如何在不停止/退出程序的情况下捕获并打印完整的异常回溯?

我想在不退出的情况下捕获并记录异常,例如,

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 拦截异常的情况下引发异常时打印的完全相同的输出,并且我不希望它退出我的程序。

不是一个完整的答案,但有人可能想知道您可以访问大量查看 err.__traceback__ 的信息(至少在 Python 3.x 中)
人们在试图找出如何打印他们的堆栈跟踪时查看了它 825k 次。这是 Python 的另一个禅宗。
似乎我是世界上唯一一个想要在没有错误的情况下打印堆栈的人(= 只是为了看看我是如何在这条精确的线路上到达这里的(这不是我的代码,而且它是如此丑陋,我无法弄清楚它是如何做到的)确实来过这里!))。
这个问题的所有答案都是调试python代码的终极初学者指南

t
taras

如果您需要,traceback.format_exc()sys.exc_info() 将产生更多信息。

import traceback
import sys

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

print(sys.exc_info()[0] 打印 <class 'Exception'>
不要使用 exc... 回溯包含所有信息 stackoverflow.com/questions/4564559/…
print(sys.exc_info()[2]) 产生 <traceback object at 0x0000028A79E6B2C8>
print(traceback.format_exc()) 优于 traceback.print_tb(exc.__traceback__)print(sys.exc_info()) 返回整个元组,看起来像 (<class 'UnicodeDecodeError'>, UnicodeDecodeError('utf-8', b'\x81', 0, 1, 'invalid start byte'), <traceback object at 0x7f179d64ae00>) 所以确实 traceback.format_exc() 非常优秀,因为它打印 Traceback (most recent call last): File "<ipython-input-15-9e3d6e01ef04>", line 2, in <module> b"\x81".decode() UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte
有什么想法可以在树莓派 0 上使用回溯吗?
M
Mr_and_Mrs_D

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

请注意,使用 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 !?!

如果您确实需要访问原始 traceback,一种解决方案是将 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!")

d
dimo414

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

traceback.print_stack()

无需手动引发异常来再次捕获它。


回溯模块正是这样做的——引发并捕获异常。
顺便说一句,默认情况下输出到 STDERR。没有出现在我的日志中,因为它被重定向到其他地方。
@pppery 我在 python 3.8 中看不到它。 trycatch 的问题是它不显示完整的回溯,仅从 raiseexcept
R
Russia Must Remove Putin

如何在不停止程序的情况下打印完整的回溯?

当您不想因错误而停止程序时,您需要使用 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)

可以调整记录级别和输出,无需触摸代码即可轻松关闭。通常做直接需要的事情是最有效的方法。


如上所述,对我来说也是如此,traceback.print_exc() 仅返回最后一次调用:您如何成功返回堆栈的多个级别(可能还有所有 levele s?)
@geekobi 我不确定你在这里问什么。我演示了我们可以追溯到程序/解释器的入口点。你有什么不清楚的?
@geekobi 的意思是,如果你抓住并重新加注,则 traceback.print_exc() 只会返回重新加注堆栈,而不是原始堆栈。
@fizloki 你是如何“重新筹集”的?您是在做一个简单的 raise 或异常链接,还是在隐藏原始回溯?见stackoverflow.com/questions/2052390/…
感谢您提出后续问题 - 我认为 exc_info=True 参数实际上更适合记录,关键字参数比将回溯放入字符串的自定义代码更易于维护。我会尽快更新我的答案。
N
NelsonGon

首先,不要使用 print 进行日志记录,有一个稳定、经过验证且经过深思熟虑的 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 调用 logger 或使用 sys.exc_info 弄脏他们的手?

好吧,只是因为!它们都存在于不同的目的。例如,traceback.print_exc 的输出与解释器本身产生的回溯略有不同。如果你使用它,你会迷惑任何阅读你日志的人,他们会用头撞他们。

传递 exc_info=True 来记录调用是不合适的。 但是,当捕获可恢复的错误并且您还想用回溯记录它们(例如使用 INFO 级别)时,它很有用,因为 log.exception 只生成一个级别的日志 - ERROR .

而且您绝对应该尽可能避免与 sys.exc_info 混淆。它只是不是一个公共接口,它是一个内部接口 - 如果您明确知道自己在做什么,您可以使用它。它不仅仅用于打印异常。


它也不能按原样工作。不是这个。我现在还没有完成:这个答案只是浪费时间。
我还要补充一点,您可以只执行 logging.exception()。除非您有特殊要求,否则无需创建日志实例。
我觉得这个答案有点荒谬。它充满了“仅仅因为这样做/不这样做”而没有解释原因。您在“为什么更好?”中的观点几乎所有人都在说同样的话:“因为它是。”我觉得这没有帮助。你至少解释了一点。
很好的信息(我不知道 logging.exception),但有点居高临下。我认为这是由于语言障碍而不是恶意意图。
这人说的。在我的公司,我们会解雇任何使用打印记录的人。 /s
C
Ciro Santilli Путлер Капут 六四事

traceback.format_exception(exception_object)

如果您只有异常对象,则可以从 Python 3 中的任何代码点获取作为字符串的回溯:

import traceback

''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))

完整示例:

#!/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)

输出:

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.9/library/traceback.html#traceback.format_exception

另请参阅:Extract traceback info from an exception object

在 Python 3.9 中测试


为什么在语言中这样做的唯一方法是两次传递相同的信息(exc_objexc_obj.__traceback__),以及不相关的第三个参数 None
N
Neuron

除了 Aaron Hall's answer,如果您正在记录,但不想使用 logging.exception()(因为它记录在 ERROR 级别),您可以使用较低级别并通过 exc_info=True。例如

try:
    do_something_that_might_error()
except Exception:
    logging.info('General exception noted.', exc_info=True)

这在处理检测到的日志记录失败时也很好......即当您由于某种原因未能创建实际的 Logger 对象时。
b
bgdnlp

我没有在任何其他答案中看到这一点。如果您出于某种原因传递 Exception 对象...

在 Python 3.5+ 中,您可以使用 traceback.TracebackException.from_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。要在标准输出上打印完整的跟踪,我们必须在最高(最低?)级别捕获它:

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

请注意堆栈打印不同,缺少第一行和最后一行。因为它是 different format()

尽可能远离引发异常的地方截取异常可以简化代码,同时提供更多信息。


这比以前的方法好很多,但是仅仅为了打印出堆栈跟踪仍然非常复杂。 Java 需要更少的代码 FGS。
s
soumya-kole

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

n
nupanick

如果你已经有一个 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)。为什么你会想要打印出一个错误,以及一个不属于该错误的回溯,这超出了我的理解。


I
Ivo van der Wijk

您需要将 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 中失败的语句尽可能具体地包装在尽可能最内层的循环中。


E
Edward Newell

为了获得精确的堆栈跟踪,作为一个字符串,如果没有 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 是个好主意,这样您仍然可以使用 Ctrl-C 终止程序。日志记录超出了问题的范围,但一个不错的选择是 loggingsystraceback 模块的文档。


这在 Python 3 中不起作用,需要更改为 desired_trace = traceback.format_exc()。将 sys.exc_info() 作为参数传递从来都不是正确的做法,但在 Python 2 中会被默默地忽略——但在 Python 3(无论如何是 3.6.4)中不会。
KeyboardInterrupt 不是(直接或间接)从 Exception 派生的。 (两者都派生自 BaseException。)这意味着 except Exception: 永远不会捕获 KeyboardInterrupt,因此 except KeyboardInterrupt: raise 是完全没有必要的。
traceback.format_exc(sys.exc_info()) 不适用于 python 3.6.10
B
Basj

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

回溯(最后一次调用):文件“C:\Users\User\Desktop\test.py”,第 7 行,在地狱 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')

A
Alexander Serkin
import io
import traceback

try:
    call_code_that_fails()
except:

    errors = io.StringIO()
    traceback.print_exc(file=errors)
    contents = str(errors.getvalue())
    print(contents)
    errors.close()

2 条评论:之前的答案中已经讨论了 traceback.print_exc() 的使用。更重要的是,当最后五行与 traceback.print_exc() 完全等价时,为什么还要对 io.StringIO 搞砸呢?
@joanis 我相信如果您想访问错误正文而不只是打印它,这些行很有用。我个人发现它很有用,因为我正在将堆栈跟踪记录到数据库中。
@pcko1 感谢您的评论,我很高兴知道这个变体有一个很好的用例。
n
nmichaels

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


N
Neuron

蟒蛇3解决方案

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 = "\n".join(list(map(str, trace)))
    trace_str = msg + "\n" + trace_str
    return trace_str

A
Ahmad

这是我在日志文件和控制台上写入错误的解决方案:

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("\nDate:" + 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

A
A.I.

你可以这样做:

try:
    do_stuff()
except Exception, err:
    print(Exception, err)
    raise err