在程序关闭期间捕获 Python 中的 KeyboardInterrupt

139

我正在用Python编写一个命令行实用程序。由于它是生产代码,因此应该能够在关闭时干净地关闭,而不会将一大堆内容(错误代码、堆栈跟踪等)倾泻到屏幕上。这意味着我需要捕捉键盘中断。

我已经尝试过使用try catch块,例如:

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print 'Interrupted'
        sys.exit(0)

并捕获信号本身(如此帖子中所述):

import signal
import sys

def sigint_handler(signal, frame):
    print 'Interrupted'
    sys.exit(0)
signal.signal(signal.SIGINT, sigint_handler)

这两种方法在正常操作期间似乎都能很好地工作。但是,如果中断发生在应用程序末尾的清理代码期间,Python似乎总是向屏幕打印一些内容。捕获中断可以

^CInterrupted
Exception KeyboardInterrupt in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802852b90>> ignored

处理这个信号会得到以下结果:

^CInterrupted
Exception SystemExit: 0 in <Finalize object, dead> ignored
或者
^CInterrupted
Exception SystemExit: 0 in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802854a90>> ignored

这些错误不仅难看,而且对于没有源代码的最终用户来说也没有什么帮助!

该应用程序的清理代码相当庞大,因此存在实际用户遇到此问题的可能性。 是否有任何方法可以捕获或阻止此输出,还是我必须处理它?


2
为什么不替换 sys.stdout/sys.stderr 呢?比如 sys.stderr = open(os.devnull, 'w')。如果你真的不关心最终的输出,那这似乎是显而易见的解决方案。 - Bakuriu
2
有一个 os._exit 但对我而言它看起来像是鼻妖。你的清理代码在哪里,你是否使用 atexit 模块来做清理工作? - wim
1
@Bakuriu:虽然重定向 stderr 可以使输出变得安静,但它也会扼杀用户可以采取措施解决的合法错误,比如文件未找到或主机不可达。 - Dan
4
@Dan 不一定要使用/dev/null。您可以编写一个自定义的文件对象,仅隐藏特定格式的消息。 - Bakuriu
2
@Bakuriu:对我来说,这仍然看起来很粗糙。如果必须这样做,我会这样做,但我觉得这应该是语言内置的东西。 - Dan
第三丹的答案不是正确的吗? - Alexey
2个回答

178

请查看此问题串,其中包含一些有关退出和回溯的有用信息。

如果您只想终止程序,请尝试类似于以下代码(这也会使清理代码失效):

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('Interrupted')
        try:
            sys.exit(130)
        except SystemExit:
            os._exit(130)

[根据评论建议更改退出代码。130是通过Ctrl-C终止脚本时在Linux上通常返回的代码。虽然我们可能不在Linux上,但重要的是返回一个非零值,而130与其他任何值一样好。]


18
我建议排除 as,然后获取并重复使用退出代码。 - wizzwizz4
10
是的。在 KeyboardInterrupt 时,绝对不应该使用 0 退出。如果有人在脚本、管道或其他任何地方使用你的脚本,他们将认为你的代码已正常执行。 - user3342816
10
Linux通常以130退出:128 + 2 http://tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF,Python中找不到任何跨平台的退出代码函数。但是`1`明显比`0`更好。 - user3342816
3
如果用户从 Bash 脚本中调用 Python 程序,并在脚本中使用 set -e 选项(应该这样做),则当用户按下 CTRL+C 后,您需要中断整个 Bash 脚本。 这将需要返回一个非零的退出代码。 - max
2
@Josiah - 这是一个非常古老的线程(约8年)。 话虽如此,当我在2014年写这篇文章时,我有一些特定的用例想法,我认为它与让Python记录器完成刷新/关闭任何打开的文件等有关。 参考此处以了解两个调用之间的区别https://dev59.com/G2kw5IYBdhLWcg3w4eYD#9591402 - Dan Hogan
显示剩余7条评论

14

在开始清理代码之前,您可以通过调用signal.signal(signal.SIGINT, signal.SIG_IGN)来忽略关机后发出的SIGINT信号。


1
如果代码预计在其他脚本中使用,那么在清理代码后应该返回 signal.SIGINTsignal.SIG_DFL,以将其恢复为默认状态。 - StickySli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接