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


77

我正在用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 2014年

1
有一个,os._exit但在我看来,它就像是鼻恶魔。您的清理代码在哪里,您是否正在使用atexit模块?
2014年

1
@Bakuriu:虽然重定向stderr将使输出静默,但它还可以压缩用户可以执行某些操作的合法错误,例如文件未找到或主机无法访问。
2014年

4
@Dan不一定是/dev/null。您可以编写一个类似文件的自定义对象,该对象仅隐藏具有给定格式的消息。
Bakuriu 2014年

2
@Bakuriu:在我看来还是很hacky。如果需要,我会这样做,但是我觉得这是应该内置在语言中的东西。
2014年

Answers:


110

检出此线程,它具有一些有关退出和回溯的有用信息。

如果您对杀死程序更感兴趣,请尝试这样的操作(这也将使清理代码下的麻烦):

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

5
我建议除外as,然后获取并重用退出代码。
wizzwizz4

5
是。一定不要0在KeyboardInterrupt上退出。如果有人在脚本,管道或任何其他人中使用您的脚本,他们会认为您的代码正常执行。
user3342816

4
Linux通常以130退出:128 + 2 tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF,在python上找不到任何跨平台退出代码功能。但是1绝对好过了0
user3342816

我不同意拥有更好的退出代码。就是说,提供的代码是来自OP的复制粘贴,通常,当您按ctrl-c进行操作时,您实际上并不关心退出代码,因为其他操作已经非常错误。
丹·霍根

1
如果用户从bash脚本调用python程序,并且他们set -e在脚本中使用了(应按要求使用),则您希望在用户按下CTRL + C之后中断整个bash脚本。这将需要返回非零的退出代码。
最多

6

在关闭开始后,可以通过signal.signal(signal.SIGINT, signal.SIG_IGN)在启动清理代码之前调用来忽略SIGINT 。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.