遗憾的是,异步异常处理并不可靠(信号处理程序引发的异常、通过 C API 的外部上下文等)。如果代码中对哪一段代码负责捕获异步异常进行了一些协调(除了非常关键的函数之外,调用堆栈中的最高可能值似乎是合适的),那么您可以增加正确处理异步异常的机会。
被调用的函数(dostuff
)或堆栈下方的函数本身可能会捕获您没有/无法解释的 KeyboardInterrupt 或 BaseException 。
这个简单的案例在 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
EDIT:
经过进一步调查,我尝试了我认为其他人用来重现该问题的示例。我太懒了所以省略了“终于”
>>> def foo():
... try:
... sys.stdin.read()
... except KeyboardInterrupt:
... print "BLAH"
...
>>> foo()
按 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
我没有按 CTRL+C 就立即引发了异常。
这似乎是有道理的——看来我们正在处理 Python 中如何处理异步异常的细微差别。在实际弹出异步异常然后在当前执行上下文中引发之前,可能需要几个字节码指令。 (这是我过去玩它时看到的行为)
请参阅 C API:http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc 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
自定义信号处理程序与解释器的标准键盘中断/CTRL+C 处理程序的疯狂混合可能会导致这种行为。例如, read() 调用看到信号并进行保释,但它在取消注册其处理程序后重新引发信号。如果不检查解释器代码库,我就无法确定。
这就是为什么我通常回避使用异步异常......
EDIT 2
我认为错误报告是一个很好的案例。
再次更多理论...(仅基于阅读代码)请参阅文件对象源:
file_read 调用 Py_UniversalNewlineFread()。 fread 可以返回错误,errno = EINTR(它执行自己的信号处理)。在这种情况下,Py_UniversalNewlineFread() 会终止,但不会使用 PyErr_CheckSignals() 执行任何信号检查,以便可以同步调用处理程序。 file_read 清除文件错误,但也不调用 PyErr_CheckSignals()。
有关如何使用它的示例,请参阅 getline() 和 getline_via_fgets()。该模式记录在针对类似问题的错误报告中:(http://bugs.python.org/issue1195 http://bugs.python.org/issue1195)。因此,信号似乎是由解释器在不确定的时间处理的。
我想深入研究没有什么价值,因为仍然不清楚 sys.stdin.read() 示例是否是“dostuff()”函数的正确模拟。 (可能存在多个错误)