取消过程后,Ctrl + C一次发送EOFError

Mau*_*kye 6 python exception input interrupt python-3.x

我正在使用python创建一个非常基本的命令行实用程序,并且试图将其设置为使用户能够在执行Ctrl + C的过程中中断任何正在运行的操作,并通过发送EOF并退出程序来退出程序Ctrl + Z。但是,在最后一天,我一直在解决这个令人沮丧的问题,在取消使用a的运行操作后,再次KeyboardInterrupt按Ctrl + C会发送EOFError而不是a KeyboardInterrupt,这将导致程序退出。KeyboardInterrupt除了在我输入任何命令或空行(在其中发送附加命令KeyboardInterrupt而不是我提供的输入)之前,随后按Ctrl + C会一直发送。这样做之后,再次按Ctrl + C将再次发送EOFError,然后从那里继续。这是演示我的问题的代码的最小示例;

import time

def parse(inp):
    time.sleep(1)
    print(inp)
    if inp == 'e':
        return 'exit'

while True:
    try:
        user_in = input("> ").strip()
    except (KeyboardInterrupt, Exception) as e:
        print(e.__class__.__name__)
        continue

    if not user_in:
        continue

    try:
        if parse(user_in) == 'exit':
            break
    except KeyboardInterrupt:
        print("Cancelled")
Run Code Online (Sandbox Code Playgroud)

这是我的代码的一些示例输出;

>
>
>
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
>
>
> ^Z
EOFError
> ^Z
EOFError
> ^Z
EOFError
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
>
> ^Z
EOFError
> KeyboardInterrupt
>
>
Run Code Online (Sandbox Code Playgroud)

如您所见,当按Ctrl + C,Ctrl + Z或空白行时,提示符将按照您期望的那样正确响应每个错误。但是,如果我运行命令并尝试在执行过程中通过按Ctrl + C取消它;

> test
Cancelled
>
>
>
> EOFError
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
KeyboardInterrupt
>
>
> EOFError
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
KeyboardInterrupt
>
>
>
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,我只按了Ctrl + C,然后按Enter。我先按Enter键发送几个空行,然后按Ctrl + C,首先发送一个EOFError。之后按Ctrl + C多个时间,然后KeyboardInterrupt正确发送。之后,先发送空白行,然后发送KeyboardInterrupt,然后正常接收随后输入的空白​​行。从程序的执行开始重复此功能。

为什么会发生这种情况,我该如何解决?

tit*_*ban 4

所以。您发现了一个相当古老的 Python bug。这与键盘中断的异步性质有关,以及如果您KeyboardInterrupt在挂起时向 Python 发送 a ,并且它不响应中断,第二个中断将引发更强的EOFError。然而,这两者似乎发生了冲突,如果你有一个异步KeyboardInterrupt捕获,然后是第二个输入KeyboardInterrupt,那么某些缓冲区中会留下一些东西,从而触发了EOFError。

我知道这不是一个很好的解释,也不是很清楚。然而,它允许一个非常简单的修复。让缓冲区赶上所有异步中断,然后开始等待输入:

import time

def parse(inp):
    time.sleep(1)
    print(inp)
    if inp == 'e':
        return 'exit'

while True:
    try:
        user_in = input("> ").strip()
    except (KeyboardInterrupt, Exception) as e:
        print(e.__class__.__name__)
        continue

    if not user_in:
        continue

    try:
        if parse(user_in) == 'exit':
            break
    except KeyboardInterrupt:
        print("Cancelled")
        time.sleep(0.1)    # This is the only line that's added
Run Code Online (Sandbox Code Playgroud)

现在执行与您相同的操作会产生以下结果:

> test
Cancelled
>
>
>
>
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
> KeyboardInterrupt
>
>
> KeyboardInterrupt
> KeyboardInterrupt
>
Run Code Online (Sandbox Code Playgroud)