无法在命令提示符下捕获 KeyboardInterrupt 两次?

Cannot catch KeyboardInterrupt in command prompt twice?

今天,我不得不检查我的脚本 运行 如何在 Windows 命令提示符 [1] 上运行,这时我发现了一些奇怪的事情。我正在研究与此类似的东西,但这足以证明问题所在。这是代码。

def bing():
    try:
        raw_input()
    except KeyboardInterrupt:
        print 'This is what actually happened here!'

try:                     # pardon me for those weird strings
    bing()               # as it's consistent with everything in the chat room (see below)
    print 'Yoo hoo...'
except KeyboardInterrupt:
    print 'Nothing happens here too!'

情况是这样的。当脚本 运行s 时,它等待输入并且用户应该按 Ctrl+C 以引发 KeyboardInterrupt 将(应该)被 bing() 中的 except 块捕获。所以,这应该是实际的输出。而且,这就是当我在我的 Ubuntu 终端和 IDLE 中 运行 它时发生的情况(在 Windows 和 Ubuntu 上)。

This is what actually happened here!
Yoo hoo...

但是,这在 Windows 命令提示符下并不像预期的那样。我宁愿得到一个奇怪的输出。

This is what actually happened here! Nothing happens here too!

看起来像一个KeyboardInterrupt在整个程序中传播并最终终止它。

我尽力了。首先,我使用 signal.signal 来处理 SIGINT (这没有用),然后我使用处理函数来引发一个 Exception ,我稍后会捕获它(它没有' t 工作),然后事情变得比以前更复杂。所以,我回到了我的老 try... catch。然后,我去了Pythonists的房间。

@poke suggested that an EOFError is raised when we press Ctrl+C. Then, @ZeroPiraeus said 当按下 Ctrl+Z 时会引发 EOFError输入.

这很有帮助,推动了 discussion after a few minutes of fiddling around. Soon, everything became chaos! Some results were good, some were unexpected, and a few went haywire!

结论是停止使用 Windows 并请我的朋友使用终端(我同意)。但是,我可以通过捕获 EOFErrorKeyboardInterrupt 来解决问题。虽然每次按 Ctrl+ZEnter 感觉很懒,但这不是什么大问题我。但是,这对我来说是一种痴迷。

在进一步研究中,我还注意到当我按下 Ctrl+C 时,CMD 上没有出现 KeyboardInterrupt .

底部什么都没有。那么,这里到底发生了什么?为什么 KeyboardInterrupt 传播?有没有办法(完全)使输出与终端一致?


[1]:我一直在终端上工作,但今天我需要确保我的脚本在所有平台上都能正常工作(特别是因为我的大多数朋友都不是程序员,只是坚持到 Windows).

问题user2357112 linked, explains this in some way: Why can't I handle a KeyboardInterrupt in python?.

键盘中断是异步产生的,所以它不会立即终止应用程序。相反,Ctrl+C 是在某种需要一段时间才能到达那里的事件循环中处理的。不幸的是,这意味着在这种情况下您无法可靠地捕获 KeyboardInterrupt 。但是我们可以做一些事情来实现目标。

正如我昨天解释的那样,停止 raw_input 调用的异常不是 KeyboardInterrupt,而是 EOFError。您可以通过像这样更改 bing 函数来轻松验证这一点:

def bing():
    try:
        raw_input()
    except Exception as e:
        print(type(e))

您会看到打印的异常类型是 EOFError 而不是 KeyboardInterrupt。您还会看到 print 甚至没有完全通过:没有新行。这显然是因为输出被在 print 语句将异常类型写入 stdout 之后到达的中断打断了。当您向打印中添加更多内容时,您也可以看到这一点:

def bing():
    try:
        raw_input()
    except EOFError as e:
        print 'Exception raised:', 'EOF Error'

请注意,我在这里为打印语句使用了两个单独的参数。当我们执行这个时,我们可以看到“Exception raised”文本,但是“EOF Error”不会出现。相反,来自外部调用的 except 将触发并捕获键盘中断。

尽管如此,Python 3 中的事情变得更加失控。拿这个代码:

def bing():
    try:
        input()
    except Exception as e:
        print('Exception raised:', type(e))

try:
    bing()
    print('After bing')
except KeyboardInterrupt:
    print('Final KeyboardInterrupt')

这几乎就是我们之前所做的,只是针对 Python 3 语法进行了修改。如果我 运行 这个,我得到以下输出:

Exception raised: <class 'EOFError'>
After bing
Final KeyboardInterrupt

所以我们可以再次看到,EOFError 被正确捕获,但由于某种原因 Python 3 继续执行的时间比 Python 2 长很多,因为打印 after bing() 也被执行。更糟糕的是,在使用 cmd.exe 的某些执行中,我得到的结果是根本没有捕获到键盘中断(显然,中断在 之后 程序已经完成)。

那么如果我们想确保我们得到一个键盘中断,我们能做些什么呢?我们肯定知道的一件事是中断 input()(或 raw_input())提示 always 引发 EOFError:这是我们始终如一的事情一直都见过。所以我们能做的就是抓住它,然后确保我们得到键盘中断。

一种方法是从 EOFError 的异常处理程序中引发 KeyboardInterrupt。但这不仅感觉有点脏,而且也不能保证中断实际上是首先终止输入提示的原因(谁知道还有什么可能引发 EOFError?)。所以我们应该让已经存在的中断信号产生异常。

我们这样做的方式很简单:我们等待。到目前为止,我们的问题是,执行继续是因为异常没有足够快地到达。那么,如果我们稍等一下,让异常最终到达,然后再继续其他事情呢?

import time
def bing():
    try:
        input() # or raw_input() for Python 2
    except EOFError:
        time.sleep(1)

try:
    bing()
    print('After bing')
except KeyboardInterrupt:
    print('Final KeyboardInterrupt')

现在,我们只捕获 EOFError 并稍等片刻,让后面的异步进程安定下来并决定是否中断执行。这始终允许我在外部 try/catch 中捕获 KeyboardInterrupt 并且不会打印任何其他内容,除了我在异常处理程序中所做的。

您可能担心一秒钟的等待时间很长,但在我们中断执行的情况下,那一秒钟不会真正持续很长时间。 time.sleep 后几毫秒,中断被捕获,我们进入了异常处理程序。所以一秒钟只是一个故障保险,它会等待足够长的时间,以便异常 definitely 及时到达。在最坏的情况下,当实际上没有中断而只有“正常”的 EOFError 时?那么之前 无限阻塞用户输入 的程序将需要多一秒钟才能继续;这应该不是真正的问题(更不用说 EOFError 可能非常罕见)。

所以我们有了解决方案:抓住 EOFError,稍等片刻。至少我希望这是一个可以在我自己的机器以外的其他机器上运行的解决方案 ^_^”经过昨晚,我对此不太确定——但至少我在所有终端和不同的终端上获得了一致的体验 Python 个版本。