如何避免在python中的垃圾收集期间抑制KeyboardInterrupt?

时间:2015-10-27 09:37:38

标签: python linux exception garbage-collection signals

SIGINT的默认处理程序引发KeyboardInterrupt。但是,如果程序在__del__方法内(由于正在进行的垃圾收集),则会忽略该异常,并将以下消息打印到stderr:

Exception KeyboardInterrupt in <...> ignored

因此,尽管接收到SIGINT,程序仍继续工作。当然,我可以为SIGINT定义自己的处理程序,将全局变量sigint_received设置为True,然后经常检查程序中变量的值。但这看起来很难看。

有没有一种优雅可靠的方法来确保python程序在收到SIGINT后被中断?

1 个答案:

答案 0 :(得分:1)

在我深入研究解决方案之前,我想强调docs for object.__del__(强调我的)中可怕的红色“警告:”侧边栏:

  

由于调用__del__()方法的不稳定情况,将忽略执行期间发生的异常,并向sys.stderr打印警告。 [...] __del__()方法应该保持维持外部不变量所需的绝对最小值。

这告诉我,任何被交互式用户__del__打断的严重风险的Ctrl-C方法可能会做得太多。因此,我的第一个建议是寻找最小化__del__方法的方法,无论它是什么。

或者换句话说:如果你的__del__方法确实 做了“所需的绝对最低限度”,那么如何在中途杀死这个过程是安全的?

自定义信号处理程序

我能找到的唯一解决方案确实是signal.SIGINT sys.exit ...但很多明显的技巧都不起作用:

失败:sys.exit

从信号处理程序调用{​​{1}}刚刚引发了SystemExit异常,该异常被忽略。 Python的C API文档表明Python解释器不可能在__del__方法期间引发任何异常:

  

void custom signal handler (PyObject *obj)

     

[当......时调用]例如,当PyErr_WriteUnraisable方法中发生异常时,解释器无法实际引发异常[...]。

部分成功:标志变量

你在信号处理程序中设置一个全局“drop dead”变量的想法只能部分地起作用 - 尽管它更新了变量,但是在{{之后'没有任何机会读取该变量1}}方法返回。因此,在几秒钟内,__del__似乎没有做任何事情。

如果您只想“最终”终止进程,这可能已经足够了,因为只要Ctrl-C方法返回,退出。但是,由于您可能希望在没有等待的情况下关闭进程(__del__SIGINT通常来自不耐烦的用户),这样做不会。

成功:KeyboardInterrupt

由于我找不到说服Python解释器自杀的方法,我的解决方案是让(更有说服力的)操作系统为我做。此信号处理程序使用__del__()向其自己的进程ID发送更强的os.kill,从而导致Python解释器本身退出。

SIGTERM

设置自定义信号处理程序后,def _sigterm_this_process(signum, frame): pid = os.getpid() os.kill(pid, signal.SIGTERM) return # Elsewhere... signal.signal(signal.SIGINT, _sigterm_this_process) 导致Ctrl-C方法(以及整个程序)立即退出。