有时间限制的输入?

40

我希望能够使用input向用户提问。例如:

print('some scenario')
prompt = input("You have 10 seconds to choose the correct answer...\n")

然后,如果时间过去了,打印出类似以下的内容

print('Sorry, times up.')
任何帮助指引我正确的方向将不胜感激。

1
@interjay 在我发问之前,我已经阅读了这篇帖子。首先,我使用的是Windows平台而不是Unix。被接受的答案说它只适用于Unix,而且我相信后来回答的人说它甚至都不起作用。此外,我正在使用Python 3。我需要使用input而不是raw_input。 - cloud311
3
这个问题有多个答案,对于Francesco Frassinelli发布的这个问题也是如此,其中许多答案并不仅适用于Unix系统。你可以将raw_input简单地更改为input。另外,当你提出问题时,应该明确相关信息,例如运行在Windows上以及你已经尝试过但未能解决的解决方案,这样人们就不会浪费时间重写旧答案。 - interjay
1
Python中带有超时的键盘输入 - jfs
相关:raw_input和timeout /3471461 - n611x007
如何在输入上设置时间限制 /2933399 - n611x007
这个对我有用 https://dev59.com/9HM_5IYBdhLWcg3wiDuA#2904057 - Ritwik
2个回答

33
如果阻塞主线程时用户没有提供答案是可以接受的:
from threading import Timer

timeout = 10
t = Timer(timeout, print, ['Sorry, times up'])
t.start()
prompt = "You have %d seconds to choose the correct answer...\n" % timeout
answer = input(prompt)
t.cancel()

否则,您可以在Windows上使用@Alex Martelli的答案(已针对Python 3进行修改)(未经测试):
import msvcrt
import time

class TimeoutExpired(Exception):
    pass

def input_with_timeout(prompt, timeout, timer=time.monotonic):
    sys.stdout.write(prompt)
    sys.stdout.flush()
    endtime = timer() + timeout
    result = []
    while timer() < endtime:
        if msvcrt.kbhit():
            result.append(msvcrt.getwche()) #XXX can it block on multibyte characters?
            if result[-1] == '\r':
                return ''.join(result[:-1])
        time.sleep(0.04) # just to yield to other processes/threads
    raise TimeoutExpired

使用方法:

try:
    answer = input_with_timeout(prompt, 10)
except TimeoutExpired:
    print('Sorry, times up')
else:
    print('Got %r' % answer)

在Unix上,您可以尝试:

import select
import sys

def input_with_timeout(prompt, timeout):
    sys.stdout.write(prompt)
    sys.stdout.flush()
    ready, _, _ = select.select([sys.stdin], [],[], timeout)
    if ready:
        return sys.stdin.readline().rstrip('\n') # expect stdin to be line-buffered
    raise TimeoutExpired

或者:

import signal

def alarm_handler(signum, frame):
    raise TimeoutExpired

def input_with_timeout(prompt, timeout):
    # set signal handler
    signal.signal(signal.SIGALRM, alarm_handler)
    signal.alarm(timeout) # produce SIGALRM in `timeout` seconds

    try:
        return input(prompt)
    finally:
        signal.alarm(0) # cancel alarm

5
第一次回答在超时后确实被打印出来了,但输入仍然可用。 - Eliezer Miron
1
@EliezerMiron:是的,在第一个例子中,input()调用没有被中断,这就是为什么在示例之前有“如果阻塞主线程是可以接受的”的原因。如果您需要中断输入,请使用带有input_with_timeout()的以下示例。 - jfs
我尝试使用import signal,但是它的时间感似乎完全不对。我正在使用Cloud9 IDE。如果我设置了0.5秒的超时,它将等待约3秒钟才会超时。 - Pro Q
1
对于 Windows 选项,您可能需要将 if result[-1] == '\n': 替换为 if result[-1] == '\r':(在 vscode、pwshell、cmd 和终端应用程序上进行了测试)。 - DannyTalent
Windows版本还有几个问题需要注意。在return之前,您可能需要添加print()函数。另外,如果想要“支持”后退键,您需要添加以下代码:if ord(result[-1]) == 8: result = result[:-2]。在PyCharm控制台中测试通过。 - Julian
*nix版本对我不起作用;当我在Pycharm的调试模式下时,有一件事是要求我暂停进程以使用输入提示;另一件事是计时器即使在时间结束前我输入了有效的内容也会忽略我的输入。 - stucash

15

有趣的问题,这似乎有效:

import time
from threading import Thread

answer = None

def check():
    time.sleep(2)
    if answer != None:
        return
    print("Too Slow")

Thread(target = check).start()

answer = input("Input something: ")

7
你可以使用threading.Timer替代Threadtime.sleep(参考链接:https://dev59.com/q2Up5IYBdhLWcg3wF0dq#15533404)。Python 3中没有raw_input函数。 - jfs
10
raw_input() 如何在主线程终止?我明白 thread check() 是如何结束并将 "Too slow" 推送到标准输出(stdout)的,但是我不明白 raw_input() 是如何获取其标准输入(stdin)缓冲区或者说是如何"完成"的。 - DevPlayer
4
@DevPlayer指出的原因是,(a) 这不是Python 3,(b) 它不能工作。 - abarnert
8
为什么会被接受? - Akaisteph7
4
这个不应该被接受,它没有终止。 - Dmitri Nesteruk
是的,它不会终止。但是,如果您将打印语句表述如下:print("时间到了!按回车键退出...")那么答案仍然有用。 - Dobedani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接