无法在命令提示符下两次捕获 KeyboardInterrupt? [英] Cannot catch KeyboardInterrupt in command prompt twice?

查看:27
本文介绍了无法在命令提示符下两次捕获 KeyboardInterrupt?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

def bing():尝试:原始输入()除了键盘中断:打印这就是这里实际发生的事情!"尝试:# 原谅我那些奇怪的字符串bing() # 因为它与聊天室中的所有内容一致(见下文)打印 'Yoo hoo...'除了键盘中断:打印这里也没有发生任何事情!"

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

这就是这里真正发生的事情!哟吼...

但是,这在 Windows 命令提示符下并没有按预期进行.我宁愿得到一个奇怪的输出.

这就是这里真正发生的事情!这里也没有任何事情发生!

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

我尽力了.首先,我使用了 signal.signal 来处理 SIGINT(它不起作用),然后我使用处理函数来引发 Exception 后来我发现了它(这也不起作用),然后事情变得比以前更复杂了.所以,我回到了我的好老try...catch.然后,我去了 Pythonists 的房间.

链接的问题,在一些方式:为什么我不能在 python 中处理 KeyboardInterrupt?.

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

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

def bing():尝试:原始输入()除了作为 e 的例外:打印(类型(e))

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

def bing():尝试:原始输入()除了 EOFError 作为 e:打印异常引发:",EOF 错误"

请注意,我在这里为打印语句使用了两个单独的参数.执行此操作时,我们可以看到引发异常"文本,但不会出现EOF 错误".相反,来自外部调用的 except 将触发并捕获键盘中断.

不过,在 Python 3 中事情变得更加失控了.拿这个代码:

def bing():尝试:输入()除了作为 e 的例外:打印('异常引发:',类型(e))尝试:冰()打印('兵后')除了键盘中断:打印('最终键盘中断')

这与我们之前所做的几乎一模一样,只是针对 Python 3 语法进行了修改.如果我运行它,我会得到以下输出:

引发异常:兵后最终键盘中断

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

那么如果我们想确保我们得到键盘中断,我们该怎么做呢?我们肯定知道的一件事是中断 input()(或 raw_input())提示 always 会引发 EOFError:这是我们一直看到的一致的事情.所以我们能做的就是抓住那个,然后确保我们得到键盘中断.

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

我们这样做的方式很简单:我们等待.到目前为止,我们的问题是,由于异常没有足够快地到达,所以继续执行.那么,如果我们在继续其他事情之前等待一段时间让异常最终到达会怎样?

导入时间定义兵():尝试:input() # 或 raw_input() 用于 Python 2除了EOFError:时间.sleep(1)尝试:冰()打印('兵后')除了键盘中断:打印('最终键盘中断')

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

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

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

Today, I had to check how my script runs on the Windows command prompt[1], when I noticed something weird. I was working on something similar to this, but this is enough to demonstrate the problem. Here's the code.

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

Here's the situation. When the script runs, it waits for the input and the user is supposed to press Ctrl+C to raise a KeyboardInterrupt which would (should) be caught by the except block within bing(). So, this should be the actual output. And, this is what happens when I run it in my Ubuntu terminal and IDLE (on both Windows & Ubuntu).

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

But, this doesn't go as expected on the Windows command prompt. I rather get a strange output.

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

It looks like a single KeyboardInterrupt propagates throughout the program and finally terminates it.

I tried everything I could do. First, I used a signal.signal to handle the SIGINT (which didn't work), and then I used handling function to raise an Exception which I'd later catch (which didn't work either), and then things got more complicated than it used to be. So, I landed back to my good old try... catch. Then, I went to the room for Pythonists.

@poke suggested that an EOFError is raised when we press Ctrl+C. Then, @ZeroPiraeus said that EOFError is raised when one presses Ctrl+Z and Enter.

That was helpful, which drove the discussion after a few minutes of fiddling around. Soon, everything became chaos! Some results were good, some were unexpected, and a few went haywire!

The conclusion was to stop using Windows and ask my friends to use the Terminal (I agree). I could however do a workaround by catching the EOFError along with the KeyboardInterrupt. While it feels lazy to press Ctrl+Z and Enter each time, that's not a big problem for me. But, this is an obsession for me.

On further research, I also noticed that there's no KeyboardInterrupt raised on the CMD when I press Ctrl+C.

There's nothing at the bottom. So, what happens here anyway? Why does the KeyboardInterrupt propagate? Is there any way (at all) to make the output consistent with the terminal?


[1]: I've always worked on the terminal, but today I needed to ensure that my script works on all platforms (especially because most of my friends are non-coders and just stick to Windows).

解决方案

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

The keyboard interrupt is raised asynchronously, so it does not immediately terminate the application. Instead, the Ctrl+C is handled in some kind of event loop that takes a while to get there. This unfortunately means that you cannot reliably catch the KeyboardInterrupt in this case. But we can do some things to get there.

As I explained yesterday, the exception that stops the raw_input call is not the KeyboardInterrupt but an EOFError. You can easily verify this by changing your bing function like this:

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

You will see that the exception type that’s printed is EOFError and not KeyboardInterrupt. You will also see that the print did not even go through completely: There is no new line. That’s apparently because the output got interrupted by the interrupt which arrived just after the print statement wrote the exception type to stdout. You can see this also when you add a bit more stuff to the print:

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

Note that I’m using two separate arguments here for the print statement. When we execute this, we can see the "Exception raised" text, but the "EOF Error" won’t appear. Instead, the except from the outer call will trigger and the keyboard interrupt is caught.

Things get a bit more out of control in Python 3 though. Take this code:

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

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

This is pretty much exactly what we did before, just amended for Python 3 syntax. If I run this, I get the following output:

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

So we can again see, that the EOFError is correctly caught, but for some reason Python 3 continues the execution a lot longer than Python 2 here, as the print after bing() is executed as well. What’s worse, in some executions with cmd.exe, I get the result that no keyboard interrupt is caught at all (so apparently, the interrupt got processed after the program already completed).

So what can we do about this if we want to make sure that we get a keyboard interrupt? One thing we know for sure is that interrupting an input() (or raw_input()) prompt always raises an EOFError: That’s the one consistent thing that we have seen all the time. So what we can do is just catch that, and then make sure that we get the keyboard interrupt.

One way to do this would be to just raise a KeyboardInterrupt from the exception handler for EOFError. But this not only feels a bit dirty, it also doesn’t guarantee that an interrupt is actually what terminated the input prompt in the first place (who knows what else can possibly raise an EOFError?). So we should have the already existing interrupt signal generate the exception.

The way we do this is quite simple: We wait. So far, our problem was, that the execution continued because the exception didn’t arrive fast enough. So what if we wait a bit to let the exception eventually arrive before we continue with other things?

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

Now, we just catch the EOFError and wait a bit to let the asynchronous processes in the back settle and decide on whether to break the execution or not. This consistently allows me to catch the KeyboardInterrupt in the outer try/catch and will not print anything else except what I do in the exception handler.

You might worry that one second is a long time to wait, but in our cases, where we interrupt the execution, that second never really lasts long. Just a few milliseconds after the time.sleep, the interrupt is caught and we’re in our exception handler. So the one second is just a fail-safe that will wait long enough that the exception definitely arrives in time. And in the worst case, when there isn’t actually an interrupt but just a "normal" EOFError? Then the program that was previously blocking infinitely for user input will take a second longer to continue; that shouldn’t really be a problem ever (not to mention that the EOFError is probably super rare).

So there we have our solution: Just catch the EOFError, and wait a bit. At least I hope that this is a solution that works on other machines than my own ^_^" After last night, I’m not too sure about this—but at least I got a consistent experience over all my terminals and different Python versions.

这篇关于无法在命令提示符下两次捕获 KeyboardInterrupt?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆