Keyboard input with timeout?
Asked Answered
P

29

104

How would you prompt the user for some input but timing out after N seconds?

Google is pointing to a mail thread about it at http://mail.python.org/pipermail/python-list/2006-January/533215.html but it seems not to work. The statement in which the timeout happens, no matter whether it is a sys.input.readline or timer.sleep(), I always get:

<type 'exceptions.TypeError'>: [raw_]input expected at most 1 arguments, got 2

which somehow the except fails to catch.

Providenciaprovident answered 26/8, 2009 at 15:19 Comment(5)
related: Python 3 Timed InputGastrostomy
possible duplicate of Timeout on a Python function callOutstation
related: raw_input and timeout /3471461Outstation
related: How to set time limit on input /2933399Outstation
Platform independent, stdlib-solution for multiple inputs until timeout: linkConfound
I
51

The example you have linked to is wrong and the exception is actually occuring when calling alarm handler instead of when read blocks. Better try this:

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
Impressionable answered 26/8, 2009 at 18:50 Comment(5)
I have been struggling with getting a keyboard input with timeout today. I just wanted a way to stop the reproduction of images from the hard-drive so that I can stop it just pressing a key, so I wanted a small timeout (33ms). I just want to point out that some solutions that you'll find on stackoverflow don't work on IDLE!! (I don't know why). You have to execute them on terminal. And also, the most helpful code I have found on internet is this one: home.wlu.edu/~levys/software/kbhit.py . Good luck!Orthotropous
I was trying this solution, and this was not working in python3. You have to raise an error in interrupted function to catch that exception in defined input function - that will make it work in python3. :)Crockett
This does not work for me. It just prints "interrupted" after 5 seconds, but it does not actually stop the input. It still waits for Enter to be pressed, and it even prints any text I enter after the "Interrupted" message appears. Tested on Linux with Python 2 and 3.Reprehensible
A link referring to the library docs would be very useful in order to debug in case it doesn't work for someone.Barrio
You need to define a hanlder for this. For ex, "def handler(signum, frame): raise IOError" and then "signal.signal(signal.SIGALRM, handler)"Morocco
S
124

Using a select call is shorter, and should be much more portable

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!"
Somber answered 25/5, 2010 at 11:18 Comment(3)
I just tested and this does NOT work for windows. Select is available, but on windows the input to select can only be a socket - sys.stdin and file descriptors are unix. I'll be sure to test first next time.Corded
Darn. Well, what self respecting programmer uses windows anyway? ;) For simple user input I guess it could be done with a loop around "kbhit", which detects keyboard presses, and "getch" with "time.sleep" to break after a timeout. But it will be ugly.Somber
If you intend to read from standard input again after this call, it's a good idea to do termios.tcflush(sys.stdin, termios.TCIFLUSH) in the case that the read timed out. Otherwise, if the user entered characters but did not press Enter, the terminal emulator may allow users to press backspace and erase subsequent program output (up to the number of characters the user entered).Provolone
I
51

The example you have linked to is wrong and the exception is actually occuring when calling alarm handler instead of when read blocks. Better try this:

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
Impressionable answered 26/8, 2009 at 18:50 Comment(5)
I have been struggling with getting a keyboard input with timeout today. I just wanted a way to stop the reproduction of images from the hard-drive so that I can stop it just pressing a key, so I wanted a small timeout (33ms). I just want to point out that some solutions that you'll find on stackoverflow don't work on IDLE!! (I don't know why). You have to execute them on terminal. And also, the most helpful code I have found on internet is this one: home.wlu.edu/~levys/software/kbhit.py . Good luck!Orthotropous
I was trying this solution, and this was not working in python3. You have to raise an error in interrupted function to catch that exception in defined input function - that will make it work in python3. :)Crockett
This does not work for me. It just prints "interrupted" after 5 seconds, but it does not actually stop the input. It still waits for Enter to be pressed, and it even prints any text I enter after the "Interrupted" message appears. Tested on Linux with Python 2 and 3.Reprehensible
A link referring to the library docs would be very useful in order to debug in case it doesn't work for someone.Barrio
You need to define a hanlder for this. For ex, "def handler(signum, frame): raise IOError" and then "signal.signal(signal.SIGALRM, handler)"Morocco
V
22

if you dont care how it works, just
pip install inputimeout
and

from inputimeout import inputimeout, TimeoutOccurred

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

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

Valerie answered 22/12, 2020 at 9:38 Comment(2)
FYI: the link from PyPi has a typo, currently there are open PRs (#6/#9) to fix it. The source code is here: github.com/johejo/inputimeoutStopper
works on windows!Synaesthesia
R
17

Not a Python solution, but...

I ran in to this problem with a script running under CentOS (Linux), and what worked for my situation was just running the Bash "read -t" command in a subprocess. Brutal disgusting hack, I know, but I feel guilty enough about how well it worked that I wanted to share it with everyone here.

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

All I needed was something that waited for 30 seconds unless the ENTER key was pressed. This worked great.

Rhythmical answered 1/12, 2014 at 6:23 Comment(0)
P
13

And here's one that works on Windows

I haven't been able to get any of these examples to work on Windows so I've merged some different StackOverflow answers to get the following:


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 
Pyrophyllite answered 12/10, 2010 at 3:25 Comment(3)
I just realised I didn't need to use a thread. See the same code but without a thread at https://mcmap.net/q/205954/-raw_input-and-timeout-duplicate/…Pyrophyllite
this does not seem to work on windows. I'm running your code, verbatim with the exception of changing Print to py3 syntax, and adding a stdout.flush(). Windows7, python3.6Kaliningrad
In Python 3, substitute sys.stdout.write with print(prompt, end='', flush=True) for printing the prompt.Jumbled
K
11

Paul's answer did not quite work. Modified code below which works for me on

  • windows 7 x64

  • vanilla CMD shell (eg, not git-bash or other non-M$ shell)

    -- nothing msvcrt works in git-bash it appears.

  • python 3.6

(I'm posting a new answer, because editing Paul's answer directly would change it from python 2.x-->3.x, which seems too much for an edit (py2 is still in use)

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) 
Kaliningrad answered 23/1, 2017 at 17:33 Comment(2)
formatting is not working as i expect here. I'm stumped, have asked over at Meta: meta.stackexchange.com/q/290162/208995Kaliningrad
When I try this, I am not able to input anything (it waits 5 seconds without letting me input anything).Scab
T
6

I spent a good twenty minutes or so on this, so I thought it was worth a shot to put this up here. It is directly building off of user137673's answer, though. I found it most useful to do something like this:

#! /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()
Tribadism answered 16/9, 2014 at 4:59 Comment(1)
Great solution. Works very fine in Python3. Can't up-vote it enough.Ventricle
S
5

Following code worked for me.

I used two threads one to get the raw_Input and another to wait for a specific time. If any of the thread exits, both the thread is terminated and returned.

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()
Suburbia answered 23/3, 2017 at 12:22 Comment(0)
B
4

Here is a portable and simple Python 3 solution using threads. This is the only one that worked for me while being cross-platform.

Other things I tried all had problems:

  • Using signal.SIGALRM: not working on Windows
  • Using select call: not working on Windows
  • Using force-terminate of a process (instead of thread): stdin cannot be used in new process (stdin is auto-closed)
  • Redirection stdin to StringIO and writing directly to stdin: will still write to previous stdin if input() has already been called (see https://mcmap.net/q/205957/-write-function-result-to-stdin)
    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
Betook answered 9/1, 2019 at 13:13 Comment(1)
This kind of works, but leaves the thread running on timeout.Muller
D
4

For Linux, I would prefer the select version by @Pontus. Here just a python3 function works like read in shell:

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)

Run

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

And a yes_or_no function

In [33]: yes_or_no_3 = lambda prompt: 'n' not in timeout_input(prompt + "? (Y/n)", 3, default="y")[1].lower()                                                                                                      

In [34]: yes_or_no_3("Continue")                                                                                                                                                                                   
Continue? (Y/n): 
Out[34]: True

In [35]: yes_or_no_3("Continue")                                                                                                                                                                                   
Continue? (Y/n): no

Out[35]: False
Davison answered 26/12, 2019 at 1:56 Comment(1)
For those wondering - this works perfectly on Python 3.7 and 3.8 on Ubuntu 18.04 / 20.04 and Debian 10 (Buster). Short, simple, and works great!Calli
S
3

It's been years already, but just incase someone bumps into this like I did recently trying to solve this sort of problem, there is an easy and faster way of achieving this using the func-timeout package. It has to be installed before use for most IDEs; you can install it via pip. The above link is self explanatory, but I will give an example on how I implemented it.

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 returns the value of the method in its argument, the question() function in this case. It also allows for other arguments that are needed for the function (see documentation). If the set time elapses (5 secs here) it raises a TimedOutException and runs the code in the except block.

Strader answered 26/8, 2009 at 15:19 Comment(2)
This will never work (properly): any call to input will block indefinitely until some input is received, and there's no way to break free. Frankly, the implementation of func-timeout is quite crappy: it tries to kill the thread by repeatedly "injecting" exceptions, but it doesn't even ensure those exceptions do the job (in this case they won't), it just waits an arbitrary amount and declares the thread to have been successfully stopped. This means that stdin will remain blocked and any subsequent call to input will not work properly; any input will first go to that input call.Jumbled
(continued) ... Also, when the program terminates, a fatal error occurs because stdin is still blocked by that input call in a daemon thread: Fatal Python error: could not acquire lock for <_io.BufferedReader name='<stdin>'> at interpreter shutdown, possibly due to daemon threads.Jumbled
A
3

my cross platform solution

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
Anglican answered 30/4, 2019 at 3:45 Comment(2)
looks good, need to try this, make sense to add sleep in the while loop not to use too much cpu ?Benedick
Haven't tested this solution, but I don't think a sleep would be needed, because get() blocks until the result is available. See the docs: docs.python.org/3/library/queue.html#queue.Queue.getProductive
B
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')

Well just simply set a new Daemon thread, and set a sleep time that whatever you want for timeout. I think that is easy to catch up XD

Baerl answered 24/5, 2020 at 11:12 Comment(0)
S
2

Analogous to Locane's for windows:

import subprocess  
subprocess.call('timeout /T 30')
Salvo answered 8/9, 2016 at 12:23 Comment(1)
If it matters, timeout was introduced with or after Windows Vista.Landpoor
B
2

Modified iperov answer that works for me (python3 win10 2019-12-09)

changes to iperov:

  • replace str with sstr as str is a function in python

  • add imports

  • add sleep to lower cpu usage of the while loop (?)

  • add if name=='main': #required by multiprocessing on 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  ...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)
    
Benedick answered 9/12, 2019 at 19:18 Comment(0)
L
2

You can use in Python >= 3.4 the inputimeout lib. MIT License.

$ pip install inputimeout

from inputimeout import inputimeout, TimeoutOccurred
try:
    something = inputimeout(prompt='>>', timeout=5)
except TimeoutOccurred:
    something = 'something'
print(something)
Lambeth answered 19/7, 2021 at 18:21 Comment(2)
Why would someone name their library inputimeout instead of inputtimeout??Refuge
Why add an identical answer?Thesda
L
1

This is the way I approached this problem. I haven't tested it thoroughly, and I'm not sure it doesn't have some important problems, but considering other solutions are far from perfect as well, I decided to share:

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()
League answered 11/12, 2019 at 13:23 Comment(1)
You make another python instance? If an additional python instance is required, it'd be one I don't like it.Whizbang
L
1
import datetime, time

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
Lighthouse answered 2/1, 2023 at 20:0 Comment(0)
P
0

Solution inspired by iperov's answer which is hopefully a bit cleaner:

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))
Productive answered 5/3, 2020 at 1:46 Comment(0)
J
0

This is a Python 3.8+ (although it can be adapted to Python 3.6+) cross-platform approach that only uses threading (so no multiprocessing or calls to shell utilities). It is intended for running scripts from the command-line and isn't very suited for dynamical use.

You can wrap the builtin input function as follows. In this case I'm redefining the built-in name input as the wrapper, since this implementation requires all calls to input to be routed through this. (Disclaimer: that's why it's probably not a very good idea, just a different one, for fun.)

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

(I've defined the setup in the one-use-only _make_input_func to hide input's "static" variables in its closure, in order to avoid polluting the global namespace.)

The idea here is to make a separate thread which handles any and all calls to builtins.input, and make the input wrapper manage the timeout. Since a call to builtins.input always blocks until there is input, when the timeout is over, the special thread is still waiting for input, but the input wrapper returns (with None). At the next call, if the last call timed out, it doesn't need to call builtins.input again (since the input thread has already been waiting for input), it just prints the prompt, and then waits for said thread to return some input, as always.

Having defined the above, try running the following script:

import time

if __name__ == '__main__':
    timeout = 2
    start_t = time.monotonic()
    if (inp := input(f"Enter something (you have {timeout} seconds): ", timeout)) is not None:
        print("Received some input:", repr(inp))
    else:
        end_t = time.monotonic()
        print(f"Timed out after {end_t - start_t} seconds")

    inp = input("Enter something else (I'll wait this time): ")
    print("Received some input:", repr(inp))
    
    input(f"Last chance to say something (you have {timeout} seconds): ", timeout)
Jumbled answered 25/7, 2020 at 18:57 Comment(1)
The issue with this code is that, there is a dangling thread waiting for input forever. It might be okay if you want to exit the code after timeout. But if the code is supposed to continue, then you should kill the thread if the process times out. Using multiprocessing, you can terminate the process.Tacet
E
0

Some of the answers require to press the Enter key when the timeout occurs to continue running your code. Others seem to be convoluted, and to boot, still require to press the Enter key after timeout.

I found the answer in another thread, which works beautifully, but there's a caveat that I found. I decided to place my code in a class for portability.

Note

I had to use keyboard to inject the Enter key press, since I had another input() statement in my code. For some reason, the subsequent input() statement wouldn't appear unless I pressed the Enter key.

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

Usage

Utilities.user_input(3)

This was made with Python 3.8.3 on Windows 10.

Elliott answered 8/10, 2020 at 21:57 Comment(1)
If you are curious, the reason "subsequent input() statements" don't work is that they are buffered. When a user types, it does not send all the data to you. It buffers it. When you hit enter, it send the entire chunk.Dodiedodo
R
0

Here is one more that python 3.8+ on linux that includes a yes_no answer with default return on timeout

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

Example of use in code


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()
Radie answered 3/11, 2020 at 19:6 Comment(0)
A
0

I am using a external tool inputimeout . Source code is available at github. I know it is a external tool but it is simple and quite handy. After installing the tool use this code:

from inputimeout import inputimeout, TimeoutOccurred
try:
    something = inputimeout(prompt='>>', timeout=5)
except TimeoutOccurred:
    something = 'No input.'
print(something)
Abercromby answered 5/2, 2021 at 14:30 Comment(1)
this answer was already givenThesda
T
0

Extending the previous answer, which uses inputimeout, with a simple illustration

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}")
Toneme answered 25/6, 2022 at 7:42 Comment(0)
T
0

This is the code I've written. Using multiprocessing, we can timeout input.

from multiprocessing import Queue, Process
from queue import Empty

class ProcessTimedOutException(Exception):
    def __init__(self, message: str):
        self.message: str = message


class Terminal:

    @staticmethod
    def input_with_timeout(message: str = '', timeout: int = 60) -> Tuple[Optional[str], Optional[Exception]]:
        queue = Queue()
        err: Optional[Exception] = None
        user_input: Optional[str] = None
        input_thread = Process(target=Terminal._input_async, args=(queue, message), daemon=True)
        input_thread.start()
        try:
            user_input = queue.get(timeout=timeout)
        except Empty:
            input_thread.terminate()
            err = ProcessTimedOutException(f'process timed out')
        return user_input, err

    @staticmethod
    def _input_async(queue, message: str = ''):
        sys.stdin = open(0)
        user_input = input(message).strip()
        queue.put(user_input)


if __name__ == '__main__':
    input_message: str = 'enter anything'
    user_input, err = Terminal.input_with_timeout(message=input_message,timeout=60)
    if err is not None:
        raise err
    print(user_input)                    

Tacet answered 13/7, 2022 at 19:5 Comment(0)
L
0

I had the same problem and solved it with keyboard and kthread. As soon as you press enter, the input field disappears. This was the most important thing for me, but I couldn't make it work with other approaches.

If you want, you can install it using pip:

pip install input-timeout

Here are some examples:

        from input_timeout import InputTimeout



        i = InputTimeout(

            timeout=20,

            input_message=" >> ",

            timeout_message="'Sorry, you were not fast enough'",

            defaultvalue="slow",

            cancelbutton="esc",

            show_special_characters_warning='If you want to use special characters, you have to use alt+\\d\\d\\d\\d\nPress "ctrl" to see a complete list of all combinations!',

        ).finalvalue

        print(f"\n\nYour input was {i}")



        i = InputTimeout(

            timeout=5,

            input_message=" >> ",

            timeout_message="Sorry, you were not fast enough: ",

            defaultvalue="slow",

            cancelbutton="esc",

            show_special_characters_warning='If you want to use special characters, you have to use alt+\\d\\d\\d\\d\nPress "ctrl" to see a complete list of all combinations!',

        ).finalvalue

        print(f"\n\nYour input was {i}")



        i = InputTimeout(

            timeout=10,

            input_message=" >> ",

            timeout_message="Sorry, you were not fast enough",

            defaultvalue="Wake up!",

            cancelbutton=None,

            show_special_characters_warning=None,

        ).finalvalue

        print(f"\n\nYour input was {i}")



        i = InputTimeout(

            timeout=10,

            input_message=" >> ",

            timeout_message="Sorry, you were not fast enough",

            defaultvalue="Are you sleeping?",

            cancelbutton="esc",

            show_special_characters_warning=None,

        ).finalvalue

        print(f"\n\nYour input was {i}")



        i = InputTimeout(

            timeout=10,

            input_message=" >>",

            timeout_message="Sorry, you were not fast enough",

            defaultvalue="you are so slow",

            cancelbutton=None,

            show_special_characters_warning='If you want to use special characters, you have to use alt+\\d\\d\\d\\d\nPress "ctrl" to see a complete list of all combinations!',

        ).finalvalue

        print(f"\n\nYour input was {i}")

#output

If you want to use special characters, you have to use alt+\d\d\d\d

Press "ctrl" to see a complete list of all combinations!

 >>  babba

Your input was babba

If you want to use special characters, you have to use alt+\d\d\d\d

Press "ctrl" to see a complete list of all combinations!

alt+0192    ->  À       alt+0193    ->  Á       alt+0196    ->  Ä       alt+0194    ->  Â       

alt+0195    ->  Ã       alt+0197    ->  Å       alt+0198    ->  Æ       alt+0228    ->  ä       

alt+0224    ->  à       alt+0225    ->  á       alt+0226    ->  â       alt+0227    ->  ã       

alt+0229    ->  å       alt+0230    ->  æ       alt+0199    ->  Ç       alt+0231    ->  ç       

alt+0208    ->  Ð       alt+0240    ->  ð       alt+0203    ->  Ë       alt+0200    ->  È       

alt+0201    ->  É       alt+0202    ->  Ê       alt+0235    ->  ë       alt+0232    ->  è       

alt+0233    ->  é       alt+0234    ->  ê       alt+0207    ->  Ï       alt+0204    ->  Ì       

alt+0205    ->  Í       alt+0206    ->  Î       alt+0239    ->  ï       alt+0236    ->  ì       

alt+0237    ->  í       alt+0238    ->  î       alt+0209    ->  Ñ       alt+0241    ->  ñ       

alt+0214    ->  Ö       alt+0210    ->  Ò       alt+0211    ->  Ó       alt+0212    ->  Ô       

alt+0213    ->  Õ       alt+0216    ->  Ø       alt+0140    ->  Œ       alt+0246    ->  ö       

alt+0242    ->  ò       alt+0243    ->  ó       alt+0244    ->  ô       alt+0245    ->  õ       

alt+0248    ->  ø       alt+0156    ->  œ       alt+0138    ->  Š       alt+0223    ->  ß       

alt+0154    ->  š       alt+0222    ->  Þ       alt+0254    ->  þ       alt+0220    ->  Ü       

alt+0217    ->  Ù       alt+0218    ->  Ú       alt+0219    ->  Û       alt+0252    ->  ü       

alt+0249    ->  ù       alt+0250    ->  ú       alt+0251    ->  û       alt+0159    ->  Ÿ       

alt+0221    ->  Ý       alt+0255    ->  ÿ       alt+0253    ->  ý       alt+0168    ->  ¨       

alt+0136    ->  ˆ       alt+0180    ->  ´       alt+0175    ->  ¯       alt+0184    ->  ¸       

alt+0192    ->  À       alt+0193    ->  Á       alt+0196    ->  Ä       alt+0194    ->  Â       

alt+0195    ->  Ã       alt+0197    ->  Å       alt+0198    ->  Æ       alt+0228    ->  ä       

alt+0224    ->  à       alt+0225    ->  á       alt+0226    ->  â       alt+0227    ->  ã       

alt+0229    ->  å       alt+0230    ->  æ       alt+0199    ->  Ç       alt+0231    ->  ç       

alt+0208    ->  Ð       alt+0240    ->  ð       alt+0203    ->  Ë       alt+0200    ->  È       

alt+0201    ->  É       alt+0202    ->  Ê       alt+0235    ->  ë       alt+0232    ->  è       

alt+0233    ->  é       alt+0234    ->  ê       alt+0207    ->  Ï       alt+0204    ->  Ì       

alt+0205    ->  Í       alt+0206    ->  Î       alt+0239    ->  ï       alt+0236    ->  ì       

alt+0237    ->  í       alt+0238    ->  î       alt+0209    ->  Ñ       alt+0241    ->  ñ       

alt+0214    ->  Ö       alt+0210    ->  Ò       alt+0211    ->  Ó       alt+0212    ->  Ô       

alt+0213    ->  Õ       alt+0216    ->  Ø       alt+0140    ->  Œ       alt+0246    ->  ö       

alt+0242    ->  ò       alt+0243    ->  ó       alt+0244    ->  ô       alt+0245    ->  õ       

alt+0248    ->  ø       alt+0156    ->  œ       alt+0138    ->  Š       alt+0223    ->  ß       

alt+0154    ->  š       alt+0222    ->  Þ       alt+0254    ->  þ       alt+0220    ->  Ü       

alt+0217    ->  Ù       alt+0218    ->  Ú       alt+0219    ->  Û       alt+0252    ->  ü       

alt+0249    ->  ù       alt+0250    ->  ú       alt+0251    ->  û       alt+0159    ->  Ÿ       

alt+0221    ->  Ý       alt+0255    ->  ÿ       alt+0253    ->  ý       alt+0168    ->  ¨       

alt+0136    ->  ˆ       alt+0180    ->  ´       alt+0175    ->  ¯       alt+0184    ->  ¸       

Sorry, you were not fast enough: 

Your input was slow

 >>  super

Your input was super

 >>  adasa

Your input was adasa

If you want to use special characters, you have to use alt+\d\d\d\d

Press "ctrl" to see a complete list of all combinations!

Sorry, you were not fast enough

Your input was you are so slow
Levant answered 1/9, 2022 at 0:15 Comment(0)
W
0

In November 2022, there's a project for Python 3 by werecatf in the pypi repository called pytimedinput. It works fine on my Windows 10 system. You can install it with pip like this:

C:\Users\User> pip install pytimedinput

Here's an example of usage:

from pytimedinput import timedInput
userText, timedOut = timedInput("Enter something: ", timeout=5)
if(timedOut):
    print("Timed out when waiting for input.")
    print(f"User-input so far: '{userText}'")
else:
    print(f"User-input: '{userText}'")
Witwatersrand answered 13/11, 2022 at 0:24 Comment(0)
A
0

It's possible to do this with asyncio. Essentially combining https://mcmap.net/q/205959/-listen-to-keypress-with-asyncio and https://mcmap.net/q/205960/-how-to-properly-use-asyncio-first_completed leads to something like this:

import asyncio
import sys


async def main():
    reader = asyncio.StreamReader()
    pipe = sys.stdin
    loop = asyncio.get_event_loop()
    await loop.connect_read_pipe(lambda: asyncio.StreamReaderProtocol(reader), pipe)

    got_input = None

    async def get_input():
        nonlocal got_input
        inp = await anext(aiter(reader))
        got_input = inp.decode()

    tasks = [asyncio.create_task(asyncio.sleep(5)), asyncio.create_task(get_input())]

    await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)

    if got_input is None:
        print("Time up!")
    else:
        print("Input: ", got_input)


if __name__ == "__main__":
    asyncio.run(main())
Audacity answered 20/4, 2023 at 11:34 Comment(0)
B
-3

A late answer :)

I would do something like this:

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

I know this is not the same but many real life problem could be solved this way. (I usually need timeout for user input when I want something to continue running if the user not there at the moment.)

Hope this at least partially helps. (If anyone reads it anyway :) )

Beneath answered 14/3, 2012 at 15:50 Comment(2)
No, KeyboardInterrupt exception occurs when users sends an interrupt signal, usually by hitting Ctrl+C on the terminal.Penstock
"try" (poster) does this work for you? I don't know of any platform where KeyboardInterrupt works like this.Level

© 2022 - 2024 — McMap. All rights reserved.