键盘输入在Python中超时

时间:2009-08-26 15:19:06

标签: python timeout keyboard-input

你会如何提示用户输入一些信息但是在N秒后超时?

谷歌在http://mail.python.org/pipermail/python-list/2006-January/533215.html指向一个关于它的邮件线程,但似乎没有用。超时发生的语句,无论是sys.input.readline还是timer.sleep(),我总是得到:

  

< type'exceptions.TypeError'>:[raw_]输入最多需要1个参数,得到2

以某种方式除了没有抓住。

24 个答案:

答案 0 :(得分:79)

使用选择呼叫的时间更短,而且应该更便携

import sys, select

print "You have ten seconds to answer!"

i, o, e = select.select( [sys.stdin], [], [], 10 )

if (i):
  print "You said", sys.stdin.readline().strip()
else:
  print "You said nothing!"

答案 1 :(得分:26)

您链接到的示例是错误的,并且在调用警报处理程序而不是读取块时实际发生异常。最好试试这个:

import signal
TIMEOUT = 5 # number of seconds your want for timeout

def interrupted(signum, frame):
    "called when read times out"
    print 'interrupted!'
signal.signal(signal.SIGALRM, interrupted)

def input():
    try:
            print 'You have 5 seconds to type in your stuff...'
            foo = raw_input()
            return foo
    except:
            # timeout
            return

# set alarm
signal.alarm(TIMEOUT)
s = input()
# disable the alarm after success
signal.alarm(0)
print 'You typed', s

答案 2 :(得分:10)

不是Python解决方案,但是......

我使用在CentOS(Linux)下运行的脚本遇到了这个问题,对我的情况有用的只是在子进程中运行Bash“read -t”命令。我知道,野蛮恶心的黑客,但我对它的运作情况感到内疚,我想与大家分享。

import subprocess
subprocess.call('read -t 30', shell=True)

除非按下ENTER键,否则我需要的是等待30秒的东西。这很有效。

答案 3 :(得分:5)

这是适用于Windows的一个

我无法让这些示例中的任何一个在Windows上运行,因此我合并了一些不同的StackOverflow答案以获得以下内容:


import threading, msvcrt
import sys

def readInput(caption, default, timeout = 5):
    class KeyboardThread(threading.Thread):
        def run(self):
            self.timedout = False
            self.input = ''
            while True:
                if msvcrt.kbhit():
                    chr = msvcrt.getche()
                    if ord(chr) == 13:
                        break
                    elif ord(chr) >= 32:
                        self.input += chr
                if len(self.input) == 0 and self.timedout:
                    break    


    sys.stdout.write('%s(%s):'%(caption, default));
    result = default
    it = KeyboardThread()
    it.start()
    it.join(timeout)
    it.timedout = True
    if len(it.input) > 0:
        # wait for rest of input
        it.join()
        result = it.input
    print ''  # needed to move to next line
    return result

# and some examples of usage
ans = readInput('Please type a name', 'john') 
print 'The name is %s' % ans
ans = readInput('Please enter a number', 10 ) 
print 'The number is %s' % ans 

答案 4 :(得分:5)

保罗的回答并不奏效。修改后的代码对我有用

  • windows 7 x64

  • vanilla CMD shell(例如, git-bash或其他非M $ shell)

    - 没有任何msvcrt在git-bash中有效。

  • python 3.6

(我发布了一个新的答案,因为直接编辑Paul的答案会改变它来自python 2.x - > 3.x,这对编辑来说似乎太多了(py2仍在使用中)

import sys, time, msvcrt

def readInput( caption, default, timeout = 5):

    start_time = time.time()
    sys.stdout.write('%s(%s):'%(caption, default))
    sys.stdout.flush()
    input = ''
    while True:
        if msvcrt.kbhit():
            byte_arr = msvcrt.getche()
            if ord(byte_arr) == 13: # enter_key
                break
            elif ord(byte_arr) >= 32: #space_char
                input += "".join(map(chr,byte_arr))
        if len(input) == 0 and (time.time() - start_time) > timeout:
            print("timing out, using default value.")
            break

    print('')  # needed to move to next line
    if len(input) > 0:
        return input
    else:
        return default

# and some examples of usage
ans = readInput('Please type a name', 'john') 
print( 'The name is %s' % ans)
ans = readInput('Please enter a number', 10 ) 
print( 'The number is %s' % ans) 

答案 5 :(得分:4)

如果你不在乎它是如何工作的,就
pip install inputimeout

from inputimeout import inputimeout, TimeoutOccurred

if __name__ == "__main__":
    try:
        c = inputimeout(prompt='hello\n', timeout=3)
    except TimeoutOccurred:
        c = 'timeout'
    print(c)

很简单
https://pypi.org/project/inputimeout/

答案 6 :(得分:3)

我花了大约二十分钟左右的时间,所以我认为值得一试将它放在这里。不过,它直接建立在用户137673的答案之上。我发现做这样的事情最有用:

#! /usr/bin/env python

import signal

timeout = None

def main():
    inp = stdinWait("You have 5 seconds to type text and press <Enter>... ", "[no text]", 5, "Aw man! You ran out of time!!")
    if not timeout:
        print "You entered", inp
    else:
        print "You didn't enter anything because I'm on a tight schedule!"

def stdinWait(text, default, time, timeoutDisplay = None, **kwargs):
    signal.signal(signal.SIGALRM, interrupt)
    signal.alarm(time) # sets timeout
    global timeout
    try:
        inp = raw_input(text)
        signal.alarm(0)
        timeout = False
    except (KeyboardInterrupt):
        printInterrupt = kwargs.get("printInterrupt", True)
        if printInterrupt:
            print "Keyboard interrupt"
        timeout = True # Do this so you don't mistakenly get input when there is none
        inp = default
    except:
        timeout = True
        if not timeoutDisplay is None:
            print timeoutDisplay
        signal.alarm(0)
        inp = default
    return inp

def interrupt(signum, frame):
    raise Exception("")

if __name__ == "__main__":
    main()

答案 7 :(得分:3)

以下代码为我工作。

我使用两个线程来获取raw_Input而另一个线程等待特定时间。 如果任何线程退出,则线程终止并返回。

def _input(msg, q):
    ra = raw_input(msg)
    if ra:
        q.put(ra)
    else:
        q.put("None")
    return

def _slp(tm, q):
    time.sleep(tm)
    q.put("Timeout")
    return

def wait_for_input(msg="Press Enter to continue", time=10):
    q = Queue.Queue()
    th = threading.Thread(target=_input, args=(msg, q,))
    tt = threading.Thread(target=_slp, args=(time, q,))

    th.start()
    tt.start()
    ret = None
    while True:
        ret = q.get()
        if ret:
            th._Thread__stop()
            tt._Thread__stop()
            return ret
    return ret

print time.ctime()    
t= wait_for_input()
print "\nResponse :",t 
print time.ctime()

答案 8 :(得分:2)

类似于Locane的Windows:

import subprocess  
subprocess.call('timeout /T 30')

答案 9 :(得分:1)

您可以在 Python >= 3.4 中使用 inputimeout 库。 麻省理工学院许可证。

$ pip install inputimeout

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

答案 10 :(得分:1)

我正在使用外部工具 inputimeout 。源代码可在 github 获得。我知道它是一个外部工具,但它很简单也很方便。 安装该工具后使用此代码:

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

答案 11 :(得分:1)

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

只需设置一个新的Daemon线程,然后将睡眠时间设置为您想要的超时时间即可。我认为这很容易赶上XD

答案 12 :(得分:1)

已经有好几年了,但是如果万一有人像我最近尝试解决这种问题那样碰到这个问题,可以使用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块中运行代码。

答案 13 :(得分:1)

这里是使用线程的可移植且简单的Python 3解决方案。 这是跨平台时唯一为我工作的人。

我尝试过的其他所有方法都有问题:

  • 使用signal.SIGALRM:在Windows上不起作用
  • 使用选择呼叫:在Windows上不起作用
  • 使用进程的强制终止(而不是线程):stdin不能在新进程中使用(stdin是自动关闭的)
  • 将stdin重定向到StringIO并直接写入stdin:如果已经调用了input(),仍将写入先前的stdin(请参见https://stackoverflow.com/a/15055639/9624704
    from threading import Thread
    class myClass:
        _input = None

        def __init__(self):
            get_input_thread = Thread(target=self.get_input)
            get_input_thread.daemon = True  # Otherwise the thread won't be terminated when the main program terminates.
            get_input_thread.start()
            get_input_thread.join(timeout=20)

            if myClass._input is None:
                print("No input was given within 20 seconds")
            else:
                print("Input given was: {}".format(myClass._input))


        @classmethod
        def get_input(cls):
            cls._input = input("")
            return

答案 14 :(得分:0)

对于Linux,我希望使用@Pontus的[root@allselenium ~]# docker-compose version docker-compose version 1.18.0, build 8dd22a9 docker-py version: 2.6.1 CPython version: 3.6.8 OpenSSL version: OpenSSL 1.0.2k-fips 26 Jan 2017 版本。在这里,只有python3函数像select在外壳中一样工作:

read

运行

import sys, select

def timeout_input(prompt, timeout=3, default=""):
    print(prompt, end=': ', flush=True)
    inputs, outputs, errors = select.select([sys.stdin], [], [], timeout)
    print()
    return (0, sys.stdin.readline().strip()) if inputs else (-1, default)

还有一个In [29]: timeout_input("Continue? (Y/n)", 3, "y") Continue? (Y/n): Out[29]: (-1, 'y') In [30]: timeout_input("Continue? (Y/n)", 3, "y") Continue? (Y/n): n Out[30]: (0, 'n') 函数

yes_or_no

答案 15 :(得分:0)

受iperov回答启发的解决方案,希望它会更清洁一些:

import multiprocessing
import sys

def input_with_timeout(prompt, timeout=None):
    """Requests the user to enter a code at the command line."""
    queue = multiprocessing.Queue()
    process = multiprocessing.Process(
        _input_with_timeout_process, args=(sys.stdin.fileno(), queue, prompt),
    )
    process.start()
    try:
        process.join(timeout)
        if process.is_alive():
            raise ValueError("Timed out waiting for input.")
        return queue.get()
    finally:
        process.terminate()


def _input_with_timeout_process(stdin_file_descriptor, queue, prompt):
    sys.stdin = os.fdopen(stdin_file_descriptor)
    queue.put(input(prompt))

答案 16 :(得分:0)

这是我解决此问题的方法。我尚未对其进行彻底的测试,并且不确定它是否存在一些重要的问题,但是考虑到其他解决方案也远非完美,我决定分享一下:

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

答案 17 :(得分:0)

适用于我的经修改的iperov答案(python3 win10 2019-12-09)

更改为iperov:

  • 用sstr替换str,因为str是python中的函数
  • 添加导入
  • 添加睡眠以降低while循环(?)的CPU使用率
  • 如果名称 =='主要'添加:#在Windows上通过多处理处理

    导入sys,os,多处理,时间

    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  ...time left '+str(tleft)+'s\ncommand:')
    
            time.sleep(2)
    
        p.terminate()
        sys.stdin = os.fdopen( sys.stdin.fileno() )
        return inp
    
    if __name__=='__main__':
        input_in_time("command:", 17)
    

答案 18 :(得分:0)

这是Python 3.8+(尽管可以适应Python 3.6 +)跨平台方法,仅使用threading (因此,{ {1}}或对Shell实用程序的调用)。它专门用于从命令行运行脚本,并不适合动态使用。

您可以如下包装内置的multiprocessing函数。在这种情况下,我将内置名称input重新定义为包装器,因为此实现要求对input的所有调用都必须通过此路由。 (免责声明:这就是为什么它不是一个好主意,只是一个有趣的想法而已。)

input

(为了避免污染全局名称空间,我已经在仅使用一次的import atexit import builtins import queue import threading def _make_input_func(): prompt_queue = queue.Queue(maxsize=1) input_queue = queue.Queue(maxsize=1) def get_input(): while (prompt := prompt_queue.get()) != GeneratorExit: inp = builtins.input(prompt) input_queue.put(inp) prompt_queue.task_done() input_thread = threading.Thread(target=get_input, daemon=True) last_call_timed_out = False def input_func(prompt=None, timeout=None): """Mimics :function:`builtins.input`, with an optional timeout :param prompt: string to pass to builtins.input :param timeout: how long to wait for input in seconds; None means indefinitely :return: the received input if not timed out, otherwise None """ nonlocal last_call_timed_out if not last_call_timed_out: prompt_queue.put(prompt, block=False) else: print(prompt, end='', flush=True) try: result = input_queue.get(timeout=timeout) last_call_timed_out = False return result except queue.Empty: print(flush=True) # optional: end prompt line if no input received last_call_timed_out = True return None input_thread.start() return input_func input = _make_input_func() del _make_input_func 中定义了设置,以将_make_input_func的“静态”变量隐藏在其闭包中。) / p>

这里的想法是创建一个单独的线程来处理对input的所有调用,并使builtins.input包装器管理超时。由于对input的调用始终阻塞,直到有输入为止,所以在超时结束时,特殊线程仍在等待输入,但是builtins.input包装器返回(带有input)。在下一次调用时,如果最后一次调用超时,则不需要再次调用None(因为输入线程已经在等待输入),它仅显示提示,然后等待该线程像往常一样返回一些输入。

已定义以上内容,请尝试运行以下脚本:

builtins.input

答案 19 :(得分:0)

某些答案需要在发生超时时按Enter键才能继续运行您的代码。其他人似乎很困惑,要引导,仍然需要在超时后按Enter键。

我找到了answer in another thread,效果很好,但是我发现了一个警告。我决定将代码放在class中以实现可移植性。

注意

由于我的代码中还有另一个keyboard语句,因此我不得不使用Enter来注入input()键。出于某种原因,除非我按下input()键,否则随后的Enter语句不会出现。

import threading
import keyboard    # https://github.com/boppreh/keyboard

class Utilities:

    # Class variable
    response = None

    @classmethod
    def user_input(cls, timeout):

        def question():
            cls.response = input("Enter something: ")

        t = threading.Thread(target=question)
        # Daemon property allows the target function to terminate after timeout
        t.daemon = True    
        t.start()
        t.join(timeout)

        if cls.response:
            # Do something
        else:
            # Do something else
            # Optional.  Use if you have other input() statements in your code
            keyboard.send("enter")

用法

Utilities.user_input(3)

这是在Windows 10上使用Python 3.8.3制作的。

答案 20 :(得分:0)

这里是linux上的python 3.8+的另一个版本,其中包含yes_no答案,默认返回超时值

import signal
def alarm_handler(signum, frame):
    raise TimeoutError
def input_with_timeout(prompt, timeout=30):
    """ get input with timeout

    :param prompt: the prompt to print
    :param timeout: timeout in seconds, or None to disable

    :returns: the input
    :raises: TimeoutError if times out
    """
    # set signal handler
    if timeout is not None:
        signal.signal(signal.SIGALRM, alarm_handler)
        signal.alarm(timeout) # produce SIGALRM in `timeout` seconds
    try:
        return input(prompt)
    except TimeoutError as to:
        raise to
    finally:
        if timeout is not None:
            signal.alarm(0) # cancel alarm

def yes_or_no(question, default='y', timeout=None):
    """ Get y/n answer with default choice and optional timeout

    :param question: prompt
    :param default: the default choice, i.e. 'y' or 'n'
    :param timeout: the timeout in seconds, default is None

    :returns: True or False
    """
    if default is not None and (default!='y' and default!='n'):
        log.error(f'bad option for default: {default}')
        quit(1)
    y='Y' if default=='y' else 'y'
    n='N' if default=='n' else 'n'
    while "the answer is invalid":
        try:
            to_str='' if timeout is None else f'(Timeout {default} in {timeout}s)'
            reply = str(input_with_timeout(f'{question} {to_str} ({y}/{n}): ',timeout=timeout)).lower().strip()
        except TimeoutError:
            log.warning(f'timeout expired, returning default={default} answer')
            reply=''
        if len(reply)==0:
            return True if default=='y' else False
        elif reply[0] == 'y':
            return True
        if reply[0] == 'n':
            return False

在代码中使用的示例


if yes_or_no(f'model {latest_model_folder} exists, start from it?', timeout=TIMEOUT):
     log.info(f'initializing model from {latest_model_folder}')
     model = load_model(latest_model_folder)
else:
     log.info('creating new empty model')
     model = create_model()

答案 21 :(得分:0)

我的跨平台解决方案

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

答案 22 :(得分:0)

麻省理工学院现在有一个图书馆。

pip install inputtimeout

https://pypi.org/project/inputimeout/

答案 23 :(得分:-4)

迟到的答案:)

我会做这样的事情:

from time import sleep

print('Please provide input in 20 seconds! (Hit Ctrl-C to start)')
try:
    for i in range(0,20):
        sleep(1) # could use a backward counter to be preeety :)
    print('No input is given.')
except KeyboardInterrupt:
    raw_input('Input x:')
    print('You, you! You know something.')

我知道这不一样,但许多现实生活中的问题都可以通过这种方式解决。 (如果用户现在不想在那里继续运行,我通常需要超时用户输入。)

希望这至少部分有帮助。 (如果有人再读它:))