在命令提示符中无法抓住KeyboardInterrupt两次? [英] Cannot catch KeyboardInterrupt in command prompt twice?

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

问题描述

今天,当我注意到有些奇怪的事情时,我必须检查我的脚本在Windows命令提示符 [1] 上的运行。我正在做类似的事情,但这足以证明这个问题。这是代码。

  def bing():
try:
raw_input()
除了KeyboardInterrupt:
print'这是在这里实际发生的'

尝试:#请原谅我那些奇怪的字符串
bing()#因为它与聊天中的所有内容一致房间(见下文)
打印'Yoo hoo ...'
除了KeyboardInterrupt:
print'这里也没有发生!'

这是情况。当脚本运行时,它等待输入,用户应该按 Ctrl + C 来提出一个 KeyboardInterrupt 应该被 bing()中的块所捕获。所以这应该是实际的输出。而且,当我在Ubuntu终端和IDLE(Windows和Ubuntu)上运行它时,会发生什么。

 这是真的发生在这里! 
Yoo hoo ...

但是,这不符合预期的Windows命令提示符。我宁愿得到一个奇怪的输出。

 这是这里实际发生的!这里也没有发生! 

它看起来像一个 KeyboardInterrupt 该程序终于终止了。



我尝试过我能做的一切。首先,我使用一个 signal.signal 来处理 SIGINT (没有工作),然后我使用处理函数来提出一个我以后捕获的()异常,然后事情比以前更复杂。所以,我回到了我好老的 try ... catch 。然后,我去了Pythonists的房间。



链接的问题解释了这一点在某些方面:为什么我无法处理python中的KeyboardInterrupt?



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



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

  def bing():
try:
raw_input()
除了例外为e:
print(type(e))
/ pre>

您将看到打印的异常类型是 EOFError 而不是 KeyboardInterrupt 。您还将看到打印甚至没有完全完成:没有新行。这显然是因为在print语句将异常类型写入stdout之后到达的中断导致中断输出。你可以看到这一点,当你添加一些更多的东西到打印:

  def bing():
try :
raw_input()
除了EOFError作为e:
print'异常提出:','EOF错误'

请注意,我在这里使用两个单独的参数作为print语句。当我们执行这个,我们可以看到异常提出文本,但是EOF错误不会出现。相反,来自外部调用的 将触发,键盘中断被捕获。



事情有点多在Python 3中失控。以下代码:

  def bing():
try:
input()
除了例外e:
print('Exception raised:',type(e))

try:
bing()
print('After bing'
除了KeyboardInterrupt:
print('Final KeyboardInterrupt')

这很漂亮我们以前做过什么,刚刚修改了Python 3语法。如果我运行这个,我得到以下输出:

 引发异常:< class'EOFError'> 
bing
最终KeyboardInterrupt

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



如果我们想确保我们得到键盘中断,那么我们可以做些什么呢?我们知道的一件事是中断一个 input()(或 raw_input())提示总是提出了一个 EOFError :这是我们一直看到的一个一致的事情。所以我们可以做的只是抓住它,然后确保我们得到键盘中断。



这样做的一个方法是提高一个 KeyboardInterrupt EOFError 的异常处理程序。但这不仅感觉有点脏,它也不能保证一个中断实际上是在第一个地方终止了输入提示(谁知道还有什么可以提出一个EOFError?)。所以我们应该有已经存在的中断信号产生异常。



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

  import time 
def bing():
try:
input()#或raw_input()for Python 2
除了EOFError:
time.sleep(1)

尝试:
bing()
print('After bing')
除了KeyboardInterrupt:
print('Final KeyboardInterrupt')

现在,我们只是抓住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天全站免登陆