为什么我不能在Python中处理KeyboardInterrupt?

我正在编写窗口上的Python 2.6.6代码,看起来像这样:

try: dostuff() except KeyboardInterrupt: print "Interrupted!" except: print "Some other exception?" finally: print "cleaning up...." print "done." 

dostuff()是一个永久循环的函数,每次从inputstream中读取一行并对其执行操作。 我想要能够阻止它,并清理时,我按Ctrl-C。

发生了什么事情, except KeyboardInterrupt:的代码根本没有运行。 唯一被打印的是“清理…”,然后回溯打印,看起来像这样:

 Traceback (most recent call last): File "filename.py", line 119, in <module> print 'cleaning up...' KeyboardInterrupt 

所以,exception处理代码没有运行,traceback声称在finally子句中发生了KeyboardInterrupt,因为按下ctrl-c是没有意义的,这就是导致该部分首先运行的原因! 即使是通用的except:子句也没有运行。

编辑:基于评论,我用sys.stdin.read()replace了try:块的内容。 问题仍然如上所述发生, finally:块的第一行运行,然后打印相同的回溯。

编辑#2:如果我添加几乎任何东西后读,处理程序的作品。 所以,这失败了:

 try: sys.stdin.read() except KeyboardInterrupt: ... 

但是这个工作:

 try: sys.stdin.read() print "Done reading." except KeyboardInterrupt: ... 

以下是打印的内容:

 Done reading. Interrupted! cleaning up... done. 

所以,出于某种原因,“完成阅读”。 行被打印,即使在上一行发生exception。 这不是一个真正的问题 – 显然我必须能够处理“try”块内的任何地方的exception。 然而,打印不能正常工作 – 它不会打印一个换行符像它应该! “Interruped”印在同一行上,前面有空格,出于某种原因…? 无论如何,之后的代码做它应该的。

在我看来,这是一个在阻塞的系统调用期间处理中断的错误。

异步异常处理不幸的是不可靠(信号处理程序引发的异常,通过C API的外部异常等)。 如果在代码中有一些关于负责捕获它们的代码(在调用堆栈中最高可能是非常关键的功能除外),那么你可以更好地处理异步异常的机会。

被调用的函数( dostuff )或函数进一步向下的函数本身可能有一个KeyboardInterrupt或BaseException的catch,你没有/不能解释。

python 2.6.6(x64)interactive + Windows 7(64bit)这个简单的例子工作得很好:

 >>> import time >>> def foo(): ... try: ... time.sleep(100) ... except KeyboardInterrupt: ... print "INTERRUPTED!" ... >>> foo() INTERRUPTED! #after pressing ctrl+c 

编辑:

经过进一步调查,我尝试了我认为是其他人用来再现问题的例子。 我很懒,所以我把“终于”

 >>> def foo(): ... try: ... sys.stdin.read() ... except KeyboardInterrupt: ... print "BLAH" ... >>> foo() 

这会在按下CTRL + C后立即返回。 当我立即尝试再次打电话给富有趣的事情发生了:

 >>> foo() Traceback (most recent call last): File "c:\Python26\lib\encodings\cp437.py", line 14, in decode def decode(self,input,errors='strict'): KeyboardInterrupt 

立即提出异常,没有我点击CTRL + C。

这似乎是有道理的 – 似乎我们正在处理如何在Python中处理异步异常的细微差别。 在异步异常被实际弹出之前,它可能需要几个字节码指令,然后在当前的执行上下文中引发异步异常。 (这是我过去玩过的行为)

请参阅C API: http : //docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc

因此,这有点解释了为什么KeyboardInterrupt在本例中的finally语句执行的上下文中被引发:

 >>> def foo(): ... try: ... sys.stdin.read() ... except KeyboardInterrupt: ... print "interrupt" ... finally: ... print "FINALLY" ... >>> foo() FINALLY Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in foo KeyboardInterrupt 

自定义信号处理程序与解释器的标准KeyboardInterrupt / CTRL + C处理程序混合在一起可能会导致这种行为。 例如,read()调用会看到信号和提示,但是在注销它的处理程序之后它会重新提高信号。 如果不检查解释器代码库,我肯定不知道。

这就是为什么我通常回避利用异步异常….

编辑2

我认为有一个错误报告的好例子。

再次更多的理论…(只是基于阅读代码)看到文件对象来源: http : //svn.python.org/view/python/branches/release26-maint/Objects/fileobject.c?revision=81277&view=markup

file_read调用Py_UniversalNewlineFread()。 fread可以返回errno = EINTR的错误(它执行自己的信号处理)。 在这种情况下Py_UniversalNewlineFread()保留,但不执行任何信号检查PyErr_CheckSignals(),以便处理程序可以同步调用。 file_read清除文件错误,但也不会调用PyErr_CheckSignals()。

有关使用方法的示例,请参阅getline()和getline_via_fgets()。 在这个错误报告中记录了类似问题的模式:( http://bugs.python.org/issue1195 )。 所以这个信号似乎是由译员在不确定的时间处理的。

我想潜水的价值不大,因为现在还不清楚sys.stdin.read()例子是否是你的“dostuff()”函数的一个适当的类比。 (可能会有多个bug)

sys.stdin.read()是一个系统调用,因此每个系统的行为将会不同。 对于Windows 7,我认为发生的事情是输入缓冲,所以你得到的地方是sys.stdin.read()返回到Ctrl-C的所有内容,只要你再次访问sys.stdin,会发送“Ctrl-C”。

尝试以下,

 def foo(): try: print sys.stdin.read() print sys.stdin.closed except KeyboardInterrupt: print "Interrupted!" 

这表明stdin的缓冲区正在导致另一个调用stdin来识别键盘输入

 def foo(): try: x=0 while 1: x += 1 print x except KeyboardInterrupt: print "Interrupted!" 

似乎没有问题。

dostuff()从标准输入读取?

有类似的问题,这是我的解决方法:

 try: some_blocking_io_here() # CTRL-C to interrupt except: try: print() # any i/o will get the second KeyboardInterrupt here? except: real_handler_here() 

以下是对发生的事情的猜测:

  • 按Ctrl-C打破“打印”语句(无论出于什么原因…错误?Win32的限制?)
  • 按Ctrl-C也会引发第一个KeyboardInterrupt,在dostuff()
  • 异常处理程序运行并尝试打印“中断”,但“打印”语句已损坏,并引发另一个KeyboardInterrupt。
  • finally子句运行并尝试打印“清理….”,但“打印”语句已损坏,并引发另一个KeyboardInterrupt。

这适用于我:

 import sys if __name__ == "__main__": try: sys.stdin.read() print "Here" except KeyboardInterrupt: print "Worked" except: print "Something else" finally: print "Finally" 

尝试在dostuff()函数之外放置一条线,或者将循环条件移到该函数的外部。 例如:

 try: while True: dostuff() except KeyboardInterrupt: print "Interrupted!" except: print "Some other exception?" finally: print "cleaning up...." print "done." 
 def foo(): try: x=0 while 1: x+=1 print (x) except KeyboardInterrupt: print ("interrupted!!") foo() 

这工作正常。