具有超时的键盘输入?

103
你如何在N秒后超时提示用户输入内容?
谷歌指向一个邮件线程(http://mail.python.org/pipermail/python-list/2006-January/533215.html),但它似乎不起作用。无论是sys.input.readline还是timer.sleep()发生超时的语句,我总是得到:
<type 'exceptions.TypeError'>: [raw_]input expected at most 1 arguments, got 2

某种情况下,异常未被捕获。


相关:Python 3定时输入 - jfs
可能是Python函数调用超时的重复问题。 - n611x007
相关:raw_input和timeout /3471461 - n611x007
如何在输入上设置时间限制 /2933399 - n611x007
跨平台的stdlib解决方案,可用于多个输入直到超时:链接 - Darkonaut
29个回答

3

我的跨平台解决方案

def input_process(stdin_fd, sq, str):
    sys.stdin = os.fdopen(stdin_fd)
    try:
        inp = input (str)
        sq.put (True)
    except:
        sq.put (False)

def input_in_time (str, max_time_sec):
    sq = multiprocessing.Queue()
    p = multiprocessing.Process(target=input_process, args=( sys.stdin.fileno(), sq, str))
    p.start()
    t = time.time()
    inp = False
    while True:
        if not sq.empty():
            inp = sq.get()
            break
        if time.time() - t > max_time_sec:
            break
    p.terminate()
    sys.stdin = os.fdopen( sys.stdin.fileno() )
    return inp

1
看起来不错,需要试一下,加入睡眠在 while 循环中是有意义的,以免使用过多的 CPU。 - kkonrad
1
还没有测试过这个解决方案,但我认为不需要使用sleep,因为get()会阻塞直到结果可用。请参阅文档:https://docs.python.org/3/library/queue.html#queue.Queue.get - Brandon

3
多年过去了,但以防万一有人像我最近尝试解决这种问题时遇到了困难,使用func-timeout包可以轻松快速地实现。 大多数IDE在使用之前都必须先安装它;您可以通过pip进行安装。 上面的链接已经很清楚了,但我将举个例子来说明如何实现它。
from func_timeout import FunctionTimedOut, func_timeout

try:
   ans = func_timeout(5, lambda: int(input('What is the sum of 2 and 3?\n')))
   print(ans)
except FunctionTimedOut:
   print(5)

func_timeout会返回其参数中方法的值,本例中为question()函数。它还允许传递其他函数需要的参数(参见文档)。 如果经过设置的时间(此处为5秒),则会引发TimedOutException并运行except块中的代码。


2
这永远不会正常工作:对input的任何调用都会无限期地阻塞,直到接收到某些输入,并且没有办法摆脱它。坦率地说,func-timeout的实现相当糟糕:它尝试通过重复“注入”异常来终止线程,但它甚至不能确保这些异常完成任务(在这种情况下,它们不会),它只是等待一定的时间并声明线程已成功停止。这意味着stdin将保持阻塞状态,并且任何后续对input的调用都将无法正常工作;任何输入都将首先进入那个 input调用。 - Anakhand
1
此外,当程序终止时,会发生致命错误,因为“stdin”仍被那个守护线程中的“input”调用阻塞:Fatal Python error: could not acquire lock for <_io.BufferedReader name='<stdin>'> at interpreter shutdown, possibly due to daemon threads - Anakhand

3
from threading import Thread
import time


def get_input():
    while True:
        print(input('> '))


t1 = Thread(target=get_input)
t1.setDaemon(True)
t1.start()
time.sleep(3)
print('program exceeds')

很简单,只需设置一个新的守护线程,并设置一个你想要的睡眠时间作为超时。我认为这很容易理解 XD


2
类似于Locane的Windows版本:
import subprocess  
subprocess.call('timeout /T 30')

2
如果有关系的话,超时是在Windows Vista之后引入的。 - DevPlayer

2

对我有效的修改过的iperov答案(python3 win10 2019-12-09)

对iperov的更改:

  • 将str替换为sstr,因为str是Python中的一个函数

  • 添加导入

  • 在while循环中添加sleep以降低CPU使用率 (?)

  • 添加if name=='main': #在Windows上需要多进程

    import sys, os, multiprocessing, time

    def input_process(stdin_fd, sq, sstr):
        sys.stdin = os.fdopen(stdin_fd)
        try:
            inp = input(sstr)
            sq.put(True)
        except:
            sq.put(False)
    
    def input_in_time(sstr, max_time_sec):
        sq = multiprocessing.Queue()
        p = multiprocessing.Process(target=input_process, args=( sys.stdin.fileno(), sq, sstr))
        p.start()
        t = time.time()
        inp = False
        while True:
    
            if not sq.empty():
                inp = sq.get()
                break
            if time.time() - t > max_time_sec:
                break
    
            tleft=int( (t+max_time_sec)-time.time())
            if tleft<max_time_sec-1 and tleft>0:
                print('\n  ...剩余时间 '+str(tleft)+'秒\n命令:')
    
            time.sleep(2)
    
        p.terminate()
        sys.stdin = os.fdopen( sys.stdin.fileno() )
        return inp
    
    if __name__=='__main__':
        input_in_time("命令:", 17)
    

2
你可以在Python >= 3.4中使用inputimeout库。 MIT许可证。
$ pip install inputimeout

from inputimeout import inputimeout, TimeoutOccurred
try:
    something = inputimeout(prompt='>>', timeout=5)
except TimeoutOccurred:
    something = 'something'
print(something)

为什么有人会把他们的库命名为inputimeout而不是inputtimeout - Tim MB
为什么加上一个完全相同的答案? - v010dya

1
import datetime

def custom_time_input(msg, seconds):
    try:
        print(msg)
        # current time in seconds
        current_time = datetime.datetime.now()
        time_after = current_time + datetime.timedelta(seconds=seconds)
        while datetime.datetime.now() < time_after:
            print("Time left: ", end="")
            print(time_after - datetime.datetime.now(), end="\r")
            time.sleep(1)
        print("\n")
        return True
    except KeyboardInterrupt:
        return False

res = custom_time_input("If you want to create a new config file PRESS CTRL+C within 20 seconds!", 20)
if res:
    pass # nothing changed
else:
    pass # do something because user pressed ctrl+c

1
这是我解决这个问题的方式。我没有进行全面测试,也不确定它是否存在一些重要问题,但考虑到其他解决方案也远非完美,我决定分享:
import sys
import subprocess


def switch():
    if len(sys.argv) == 1:
        main()
    elif sys.argv[1] == "inp":
        print(input(''))
    else:
        print("Wrong arguments:", sys.argv[1:])


def main():
    passw = input_timed('You have 10 seconds to enter password:', timeout=10)
    if passw is None:
        print("Time's out! You explode!")
    elif passw == "PasswordShmashword":
        print("H-h-how did you know you h-h-hacker")
    else:
        print("I spare your life because you at least tried")


def input_timed(*args, timeout, **kwargs):
    """
    Print a message and await user input - return None if timedout
    :param args: positional arguments passed to print()
    :param timeout: number of seconds to wait before returning None
    :param kwargs: keyword arguments passed to print()
    :return: user input or None if timed out
    """
    print(*args, **kwargs)
    try:
        out: bytes = subprocess.run(["python", sys.argv[0], "inp"], capture_output=True, timeout=timeout).stdout
    except subprocess.TimeoutExpired:
        return None
    return out.decode('utf8').splitlines()[0]


switch()

你要再创建一个 Python 实例吗?如果需要额外的 Python 实例,那就不是我喜欢的。 - H. Jang

0

我正在使用一个外部工具inputimeout。源代码可在github上找到。我知道这是一个外部工具,但它非常简单实用。

安装完工具后,请使用以下代码:

from inputimeout import inputimeout, TimeoutOccurred
try:
    something = inputimeout(prompt='>>', timeout=5)
except TimeoutOccurred:
    something = 'No input.'
print(something)

这个答案已经给出了。 - v010dya

0
延伸上一个答案,使用 inputimeout 进行简单的说明。
from inputimeout import inputimeout, TimeoutOccurred

def timed_input (user_prompt, timeout=5):
    user_input = ""
    timed_out = False
    try:
        user_input = inputimeout (prompt=user_prompt, timeout=timeout)
    except TimeoutOccurred:
        timed_out = True
    return (timed_out, user_input)

timed_out, user_input = timed_input ("Enter something within 3s... ", timeout=3)

if timed_out:
    print ("You failed to enter anything!")
else:
    print (f"You entered {user_input}")

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