【问题标题】:Python3: purge exception chain?Python3:清除异常链?
【发布时间】:2013-08-29 09:37:36
【问题描述】:

我试图在 except: 块内引发异常,但解释器试图提供帮助并“强制”打印堆栈跟踪。有没有可能避免这种情况?

一点背景资料: 我在玩 urwid,一个用于 python 的TUI library。用户界面通过调用urwid.MainLoop.run() 启动,并通过raise urwid.ExitMainLoop() 结束。到目前为止,这工作正常,但是当引发另一个异常时会发生什么?例如。当我捕捉到KeyboardInterrupt(urwid MainLoop 没有)时,我会进行一些清理并希望通过引发适当的异常来结束用户界面。但这会导致屏幕上满是堆栈跟踪。

一些小研究表明 python3 可以记住链式异常,并且可以通过“原因”显式引发异常:raise B() from A()。我学习了一些方法来更改或附加有关引发异常的数据,但我发现无法“禁用”此功能。我想避免打印堆栈跟踪和像The above exception was the direct cause of... 这样的行,而只是在except: 块内引发接口结束异常,就像我在一个块之外一样。

这是可能的还是我做错了什么?

编辑: 这是一个类似于我当前架构的示例,导致相同的问题:

#!/usr/bin/env python3
import time

class Exit_Main_Loop(Exception):
    pass

# UI main loop
def main_loop():
    try:
        while True:
            time.sleep(0.1)
    except Exit_Main_Loop as e:
        print('Exit_Main_Loop')
        # do some UI-related clean up

# my main script
try:
    main_loop()
except KeyboardInterrupt as e:
    print('KeyboardInterrupt')
    # do some clean up
    raise Exit_Main_Loop()      # signal the UI to terminate

不幸的是,我也无法将 main_loop 更改为 KeyboardInterrupt 之外的其他内容。有解决这个问题的模式吗?

【问题讨论】:

  • 您是根本不需要堆栈跟踪,还是只需要最外面的异常的堆栈跟踪,还是只需要最里面的异常的堆栈跟踪?
  • 如果您不想要 any 堆栈跟踪,只需将整个“主”代码包装在 try:/except Exception as e: 中,然后根据需要处理异常(例如,print(repr(e)); sys.exit(1))。
  • 我不希望有任何指示发生异常(对于用户)- 没有堆栈跟踪或其他文本输出(当然,除了我自己打印的内容)。我的意思是,我发现了异常并做了我必须做的事情——不再需要堆栈跟踪(如果我想阅读它,我会自己打印它)。类似地,urwid.MainLoop 也以静默方式捕获 Exit 异常。我看不出为什么仅仅因为连续有两个异常(都被正确捕获)就应该导致类似于未捕获异常的输出。
  • 我还是不明白你的问题。您能否给我们一个 SSCCE 来证明您的问题(最好是不使用 urwid 或任何其他事件循环的)?
  • 我添加了一个小例子,类似于我的“设计”和问题。

标签: python exception python-3.x urwid


【解决方案1】:

我还是不太明白你的解释,但是从代码来看:

try:
    main_loop()
except KeyboardInterrupt as e:
    print('KeyboardInterrupt')
    # do some clean up
    raise Exit_Main_Loop()      # signal the UI to terminate

main_loop 不可能看到 Exit_Main_Loop() 异常。当您到达KeyboardInterrupt 句柄时,main_loop 保证已经完成(在这种情况下,由于未处理的KeyboardInterrupt),因此其异常处理程序不再处于活动状态。

因此,发生的情况是您引发了一个没有人捕获的新异常。当异常到达代码顶部而没有被处理时,Python 会通过打印回溯并退出来自动处理它。

如果您想将一种异常类型转换为另一种类型以便main_loop 可以处理它,您必须在try 块内的某处执行此操作。

你说:

不幸的是,我也无法将 main_loop 更改为除了 KeyboardInterrupt。

如果这是真的,那么您的问题就没有真正的答案……但我不确定首先是否存在问题,除了您创建的问题。只需从您的代码中删除Exit_Main_Loop(),它不是已经在做您想要的了吗?如果您只是想阻止 Python 打印回溯并退出,这会为您解决。


如果确实存在问题——例如,main_loop 代码有一些清理代码,无论如何你都需要执行它,但它没有被执行,因为它不处理 @ 987654332@——有两种方法可以解决这个问题。


首先,正如signal 文档解释的那样:

signal.signal() 函数允许定义在收到信号时执行的自定义处理程序。安装了少量默认处理程序:…SIGINT 被转换为 KeyboardInterrupt 异常。

因此,您所要做的就是用不同的处理程序替换默认处理程序:

def handle_sigint(signum, frame):
    raise ExitMainLoop()
signal.signal(signal.SIGINT, handle_sigint)

在您开始main_loop 之前执行此操作,您应该没问题。请记住,线程程序和 Windows 存在一些限制,但如果这些限制都不适用,那么您就是黄金; ctrl-C 将触发ExitMainLoop 异常而不是KeyboardInterrupt,因此主循环将处理它。 (您可能还想在包装代码中添加一个 except ExitMainLoop: 块,以防出现异常 main_loop 之外。但是,您可以轻松编写一个 contextmanager 来设置和恢复围绕对main_loop 的调用发出信号,因此没有任何外部代码可能引发它。)


或者,即使您无法编辑main_loop 源代码,您也可以随时在运行时对其进行monkeypatch。如果不知道代码是什么样子,就不可能准确地解释如何做到这一点,但几乎总有办法做到这一点。

【讨论】:

  • 实际上main_loop() 是一个需要正确终止的外部(ui)库,这是通过引发(也是外部的)Exit_Main_Loop 来完成的。我明白你的意思,它无法像现在这样工作,但我不知道如何以更好(工作/正确)的方式对其进行建模。
  • 那么是否存在任何实际问题,因为主循环没有正确关闭您需要它执行的操作,因为它无法处理KeyboardInterrupt?如果是这样,有两种方法可以解决这个问题,但它们都至少有点难看。让我编辑答案来解释一下。
  • 好吧,即使是KeyboardInterrupt(打印的堆栈跟踪除外),它也会执行适当的清理,但在这种情况下,我也需要进行一些清理(在关闭用户界面)。我最初的回答有点……乱七八糟,主要是我对问题本身的理解有误,对不起。非常感谢您的回答,我将使用信号处理程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多