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

Jos*_*osh 38 python windows keyboardinterrupt

我正在Windows上编写python 2.6.6代码,如下所示:

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

dostuff()是一个永远循环的函数,一次从输入流读取一行并对其进行操作.我希望能够在我按下ctrl-c时停止并清理它.

发生的事情是,下面的代码except KeyboardInterrupt:根本没有运行.打印的唯一内容是"清理......",然后打印出如下所示的回溯:

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

因此,异常处理代码没有运行,并且traceback声称在finally子句期间发生了KeyboardInterrupt ,这没有意义,因为命中ctrl-c是导致该部分首先运行的原因!甚至通用except:子句也没有运行.

编辑:基于注释,我try:用sys.stdin.read()替换了块的内容.问题仍然与描述完全一致,finally:块的第一行运行,然后打印相同的回溯.

编辑#2: 如果我在阅读后添加了很多东西,那么处理程序就可以了.所以,这失败了:

try:
    sys.stdin.read()
except KeyboardInterrupt:
    ...
Run Code Online (Sandbox Code Playgroud)

但这有效:

try:
    sys.stdin.read()
    print "Done reading."
except KeyboardInterrupt:
    ...
Run Code Online (Sandbox Code Playgroud)

这是打印的内容:

Done reading. Interrupted!
cleaning up...
done.
Run Code Online (Sandbox Code Playgroud)

因此,出于某种原因,"完成阅读".即使前一行发生异常,也会打印行.这不是一个真正的问题 - 显然我必须能够在"try"块内的任何地方处理异常.但是,打印不能正常工作 - 它不会像之前那样打印换行符!"Interruped"印在同一条线上......前面有一个空格,出于某种原因......?无论如何,在那之后代码完成它应该做的事情.

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

Jer*_*own 21

遗憾的是,异步异常处理不可靠(信号处理程序引发的异常,通过C API在外部上下文中引发的异常等).如果代码中有关于哪些代码负责捕获它们的协调,则可以增加正确处理异步异常的机会(除了非常关键的函数之外,调用堆栈中最高可能是合适的).

被调用的函数(dostuff)或函数向下的函数本身可能有一个您没有/无法解释的KeyboardInterrupt或BaseException的catch.

这个简单的案例适用于python 2.6.6(x64)交互式+ Windows 7(64位):

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

编辑:

经过进一步调查,我尝试了我认为是其他人用来重现问题的例子.我很懒,所以我遗漏了"终于"

>>> def foo():
...     try:
...             sys.stdin.read()
...     except KeyboardInterrupt:
...             print "BLAH"
...
>>> foo()
Run Code Online (Sandbox Code Playgroud)

在按CTRL + C后立即返回.有趣的事情发生在我立刻再次打电话给foo时:

>>> foo()

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

在没有按下CTRL + C的情况下立即引发异常.

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

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

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

可能会有一些疯狂混合的自定义信号处理程序与解释器的标准KeyboardInterrupt/CTRL + C处理程序混合,导致这种行为.例如,read()调用会看到信号和bails,但是在取消注册它的处理程序后它会重新引发信号.如果不检查解释器代码库,我肯定不知道.

这就是为什么我通常不愿意使用异步异常....

编辑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()"函数的正确模拟.(可能会有多个错误)

  • 我不会在异步异常中将其称为"细微差别",而不是一个彻头彻尾的错误.如果异常提升的时间晚于技术上发生的时间,那很好......但是如果在异常爆发的try块外面引发异常,那就是另一个故事了.这里明确地描述了几个记录良好的行为,最重要的是"最终"块不运行"保证"的清理代码; 并且当在"try"块中明确地发生至少一个异常时,根本不运行异常处理程序. (5认同)

use*_*876 0

这是对正在发生的事情的猜测:

  • 按 Ctrl-C 会破坏“print”语句(无论出于何种原因......bug?Win32 限制?)
  • 按 Ctrl-C 也会引发第一个键盘中断,在 dostuff() 中
  • 异常处理程序运行并尝试打印“Interrupted”,但“print”语句被破坏并引发另一个键盘中断。
  • finally 子句运行并尝试打印“cleaning up....”,但“print”语句被破坏并引发另一个键盘中断。