为什么我不能在 python 中处理 KeyboardInterrupt?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4606942/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 16:31:04  来源:igfitidea点击:

Why can't I handle a KeyboardInterrupt in python?

pythonwindowskeyboardinterrupt

提问by Josh

I'm writing python 2.6.6 code on windows that looks like this:

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

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

dostuff()is a function that loops forever, reading a line at a time from an input stream and acting on it. I want to be able to stop it and clean up when I hit ctrl-c.

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

What's happening instead is that the code under except KeyboardInterrupt:isn't running at all. The only thing that gets printed is "cleaning up...", and then a traceback is printed that looks like this:

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

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

So, exception handling code is NOT running, and the traceback claims that a KeyboardInterrupt occurred during the finally clause, which doesn't make sense because hitting ctrl-c is what caused that part to run in the first place! Even the generic except:clause isn't running.

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

EDIT:Based on the comments, I replaced the contents of the try:block with sys.stdin.read(). The problem still occurs exactly as described, with the first line of the finally:block running and then printing the same traceback.

编辑:根据评论,我try:用 sys.stdin.read()替换了块的内容。问题仍然完全按照描述发生,finally:块的第一行运行,然后打印相同的回溯。

EDIT #2:If I add pretty much anything after the read, the handler works. So, this fails:

编辑 #2:如果我在读取后添加了几乎任何内容,处理程序就会工作。所以,这失败了:

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

But this works:

但这有效:

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

Here's what's printed:

这是打印的内容:

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

So, for some reason, the "Done reading." line is printed, even though the exception occurred on the previous line. That's not really a problem - obviously I have to be able to handle an exception anywhere inside the "try" block. However, the print doesn't work normally - it doesn't print a newline afterwards like it's supposed to! The "Interruped" is printed on the same line... with a space before it, for some reason...? Anyway, after that the code does what it's supposed to.

所以,出于某种原因,“读完”。行被打印,即使异常发生在前一行。这不是真正的问题 - 显然我必须能够在“try”块内的任何地方处理异常。但是,打印无法正常工作 - 之后它不会像预期的那样打印换行符!“Interrupted” 打印在同一行上......之前有一个空格,出于某种原因......?无论如何,在那之后代码会做它应该做的事情。

It seems to me that this is a bug in handling an interrupt during a blocked system call.

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

回答by user9876

Here's a guess about what's happening:

以下是对正在发生的事情的猜测:

  • pressing Ctrl-C breaks the "print" statement (for whatever reason... bug? Win32 limitation?)
  • pressing Ctrl-C also throws the first KeyboardInterrupt, in dostuff()
  • The exception handler runs and tries to print "Interrupted", but the "print" statement is broken and throws another KeyboardInterrupt.
  • The finally clause runs and tries to print "cleaning up....", but the "print" statement is broken and throws yet another KeyboardInterrupt.
  • 按 Ctrl-C 会破坏“打印”语句(无论出于何种原因......错误?Win32 限制?)
  • 按 Ctrl-C 也会在 dostuff() 中抛出第一个 KeyboardInterrupt
  • 异常处理程序运行并尝试打印“Interrupted”,但“print”语句被破坏并引发另一个 KeyboardInterrupt。
  • finally 子句运行并尝试打印“cleaning up....”,但“print”语句被破坏并抛出另一个 KeyboardInterrupt。

回答by Jeremy Brown

Asynchronous exception handling is unfortunately not reliable (exceptions raised by signal handlers, outside contexts via C API, etc). You can increase your chances of handling the async exception properly if there is some coordination in the code about what piece of code is responsible for catching them (highest possible in the call stack seems appropriate except for very critical functions).

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

The called function (dostuff) or functions further down the stack may itself have a catch for KeyboardInterrupt or BaseException that you didn't/couldn't account for.

被调用的函数 ( dostuff) 或堆栈下方的函数本身可能会捕获您没有/无法考虑的 KeyboardInterrupt 或 BaseException。

This trivial case worked just fine with python 2.6.6 (x64) interactive + Windows 7 (64bit):

这个微不足道的案例在 python 2.6.6 (x64) 交互式 + Windows 7 (64 位) 上运行得很好:

>>> import time
>>> def foo():
...     try:
...             time.sleep(100)
...     except KeyboardInterrupt:
...             print "INTERRUPTED!"
...
>>> foo()
INTERRUPTED!  #after pressing ctrl+c

EDIT:

编辑:

Upon further investigation, I tried what I believe is the example that others have used to reproduce the issue. I was lazy so I left out the "finally"

经过进一步调查,我尝试了我认为其他人用来重现该问题的示例。我很懒所以我省略了“终于”

>>> def foo():
...     try:
...             sys.stdin.read()
...     except KeyboardInterrupt:
...             print "BLAH"
...
>>> foo()

This returns immediately after hitting CTRL+C. The interesting thing happened when I immediately tried to call foo again:

这会在按 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

The exception was raised immediately without me hitting CTRL+C.

在我没有按 CTRL+C 的情况下立即引发了异常。

This would seem to make sense - it appears that we are dealing with nuances in how asynchronous exceptions are handled in Python. It can take several bytecode instructions before the async exception is actually popped and then raised within the current execution context. (That's the behavior that I've seen when playing with it in the past)

这似乎是有道理的——看来我们正在处理 Python 中异步异常处理方式的细微差别。在异步异常实际弹出然后在当前执行上下文中引发之前,它可能需要几个字节码指令。(这是我过去玩它时看到的行为)

See the C API: http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc

请参阅 C API:http: //docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc

So this somewhat explains why KeyboardInterrupt gets raised in the context of the execution of the finally statement in this example:

因此,这在一定程度上解释了为什么在此示例中执行 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

There could be some crazy mixing of custom signal handlers mixed with the interpreter's standard KeyboardInterrupt/CTRL+C handler that's resulting in this sort of behavior. For example, the read() call sees the signal and bails, but it re-raises the signal after deregistering it's handler. I wouldn't know for sure without inspecting the interpreter codebase.

可能会有一些自定义信号处理程序与解释器的标准 KeyboardInterrupt/CTRL+C 处理程序混合在一起,导致这种行为。例如, read() 调用看到信号和保释,但它在取消注册它的处理程序后重新引发信号。如果不检查解释器代码库,我就不确定。

This is why I generally shy away from making use of async exceptions....

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

EDIT 2

编辑 2

I think there's a good case for a bug report.

我认为有一个错误报告的好例子。

Again more theories...(just based on reading code) See the file object source: http://svn.python.org/view/python/branches/release26-maint/Objects/fileobject.c?revision=81277&view=markup

再次更多的理论......(仅基于阅读代码)请参阅文件对象源:http: //svn.python.org/view/python/branches/release26-maint/Objects/fileobject.c?revision=81277&view=markup

file_read calls Py_UniversalNewlineFread(). fread can return with an error with errno = EINTR (it performs its own signal handling). In this case Py_UniversalNewlineFread() bails but does not perform any signal checking with PyErr_CheckSignals() so that the handlers can get called synchronously. file_read clears the file error but also does not call PyErr_CheckSignals().

file_read 调用 Py_UniversalNewlineFread()。fread 可以通过 errno = EINTR 返回错误(它执行自己的信号处理)。在这种情况下, Py_UniversalNewlineFread() 保释但不使用 PyErr_CheckSignals() 执行任何信号检查,以便可以同步调用处理程序。file_read 清除文件错误,但也不调用 PyErr_CheckSignals()。

See getline() and getline_via_fgets() for examples of how it's used. The pattern is documented in this bug report for a similar issue: ( http://bugs.python.org/issue1195). So it seems that the signal is handled at an indeterminate time by the interpreter.

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

I guess there's little value in diving any deeper since it's still not clear whether the sys.stdin.read() example is a proper analog of your "dostuff()" function. (there could be multiple bugs at play)

我想深入研究没有什么价值,因为目前还不清楚 sys.stdin.read() 示例是否是您的“dostuff()”函数的正确模拟。(可能有多个错误在起作用)

回答by Jake

This works for me:

这对我有用:

import sys

if __name__ == "__main__":
    try:
        sys.stdin.read()
        print "Here"
    except KeyboardInterrupt:
        print "Worked"
    except:
        print "Something else"
    finally:
        print "Finally"

Try putting a line outside of your dostuff() function or move the loop condition outside of the function. For example:

尝试在 dostuff() 函数之外放置一行或将循环条件移到函数之外。例如:

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

回答by milkypostman

sys.stdin.read()is a system call and so the behavior is going to be different for each system. For windows 7 I think what is happening is that the input is being buffered and so you're getting where sys.stdin.read()is returning everything up to the Ctrl-C and as soon as you access sys.stdin again it'll send the "Ctrl-C".

sys.stdin.read()是一个系统调用,因此每个系统的行为都会不同。对于 Windows 7,我认为正在发生的事情是输入被缓冲,所以你得到的地方sys.stdin.read()是将所有内容返回到 Ctrl-C,一旦你再次访问 sys.stdin,它就会发送“Ctrl-C ”。

try the following,

尝试以下,

def foo():
    try:
        print sys.stdin.read()
        print sys.stdin.closed
    except KeyboardInterrupt:
        print "Interrupted!"

This suggests that there is a buffering of stdin going on that is causing another call to stdin to recognize the keyboard input

这表明 stdin 正在缓冲,导致对 stdin 的另一次调用以识别键盘输入

def foo():
    try:
        x=0
        while 1:
            x += 1
        print x
    except KeyboardInterrupt:
        print "Interrupted!"

there doesn't appear to be a problem.

似乎没有问题。

Is dostuff()reading from stdin?

dostuff()从标准输入读取吗?

回答by coqem2

Having similar problem and this is my workaround:

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

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

回答by Heinz Doepkemeier

def foo():
    try:
        x=0
        while 1:
            x+=1
            print (x)
    except KeyboardInterrupt:
       print ("interrupted!!")
foo()

That works fine.

这很好用。