为什么我不能用python处理键盘中断?

2024-05-16 19:37:49 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

dostuff()是一个永远循环的函数,一次从输入流读取一行并对其执行操作。我想当我按下ctrl-c键的时候能够停止它并清理它

相反,发生的是except KeyboardInterrupt:下的代码根本没有运行。唯一要打印的是“清理…”,然后打印一个如下所示的回溯:

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

因此,异常处理代码没有运行,而回溯声称在finally子句期间发生了KeyboardInterrupt,这没有意义,因为按ctrl-c是导致该部分首先运行的原因!甚至泛型except:子句也没有运行。

根据注释,我用sys.stdin.read()替换了try:块的内容。问题仍然如前所述,运行finally:块的第一行,然后打印相同的回溯。

编辑2: 如果我在读取之后添加了很多内容,处理程序就会工作。所以,这失败了:

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

但这是有效的:

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

以下是印刷品:

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

因此,出于某种原因,打印“Done reading.”行,即使前一行出现异常。这其实不是问题-显然我必须能够处理“try”块中任何地方的异常。但是,打印不正常-它不会像预期的那样打印换行符!“Interruped”印在同一行。。。前面有个空间,不知为什么。。。?不管怎么说,在那之后,代码就做了它应该做的。

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


Tags: 代码readstdinsysprintuptryexcept
3条回答

sys.stdin.read()是一个系统调用,因此每个系统的行为都将不同。对于Windows7,我认为发生的事情是输入被缓冲了,所以你得到了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()正在从stdin读取数据吗?

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

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()

不幸的是,异步异常处理是不可靠的(由信号处理程序引发的异常、通过C API的外部上下文等)。如果代码中有一些关于捕获异步异常的协调,您可以增加正确处理异步异常的机会(除了非常关键的函数之外,调用堆栈中可能的最高值似乎是合适的)。

被调用的函数(dostuff)或堆栈后面的函数本身可能有一个catch for KeyboardInterrupt或BaseException,而您没有/无法解释这个catch。

对于Python2.6.6(x64)interactive+Windows7(64位),这个简单的例子工作得很好:

>>> 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时发生了有趣的事情:

>>> 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

因此,这在一定程度上解释了为什么在本例中执行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()调用可以看到信号并执行bail操作,但在注销其处理程序后会重新发出信号。如果不检查解释器的代码库,我就无法确定。

这就是为什么我通常回避使用异步异常。。。。

编辑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()执行bail操作,但不使用PyErr_CheckSignals()执行任何信号检查,以便可以同步调用处理程序。file_read清除文件错误,但也不调用PyErr_CheckSignals()。

请参见getline()和getline_via_fgets()以获取如何使用它的示例。此错误报告中记录了类似问题的模式:(http://bugs.python.org/issue1195)。因此,似乎这个信号是在一个不确定的时间由解释器处理的。

我想再深入一点也没有什么价值,因为还不清楚sys.stdin.read()示例是否是“dostuff()”函数的正确模拟。(游戏中可能有多个bug)

相关问题 更多 >