Reputation: 287410
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.
Upvotes: 109
Views: 171226
Reputation: 501
I will focus on the Windows solution, as the Unix solution has been presented several times in this thread (using the select
API).
Some solutions suggest using the msvcrt.kbhit()
function to check if there is a user input. This function only works if the program is running in the interactive console and it does not handle multiprocessing case, which uses pipe-based communication. My solution relies on Windows API directly using the pywin32
library:
import msvcrt
import time
import win32api, win32file, win32pipe
def input_timeout(timeout: float) -> str:
"""
Waits for specified period in seconds for user input. If user input is not received in time
function raises exception.
"""
start = time.time()
handle = win32api.GetStdHandle(win32api.STD_INPUT_HANDLE)
typ = win32file.GetFileType(handle)
buffer = ""
while (time.time() - start) < timeout:
# check the type of stdin handle
if typ == win32file.FILE_TYPE_PIPE:
# limited to 1024 bytes per line !!!
data, _, _ = win32pipe.PeekNamedPipe(handle, 1024)
# usage of CR or LF depends on what data you send to subprocess
if "\n" in data:
return input()
else:
time.sleep(0.1)
else:
while msvcrt.kbhit():
ch = msvcrt.getwche()
if ch == "\x08":
buffer = buffer[:-1]
# windows interactive console uses CR character (consider handling \n too)
elif ch == "\r":
print() # add missing LF
return buffer
# possibly other control characters need to be handled here
else:
buffer += ch
time.sleep(0.1)
print("Buffer:", repr(buffer))
raise TimeoutError()
# prompt
print("Enter value (you only have 2.5 seconds): ", end="", flush=True)
val = input_timeout(2.5)
print("Value entered:", val)
I personally recommend using a dedicated thread to handle stdin input with a callback. A lot of people try to use a daemon thread to handle stdin and they rely on the system to kill the thread when the application exits, which is obviously a bad practice - you always need to clean up your mess. Here is my solution:
import threading
import time
from typing import Callable, Optional
import win32api, win32event, win32file, win32pipe
def hook_input(callback: Callable[[str], None], keep_running: threading.Event) -> threading.Thread:
"""
Creates background thread that waits for input on stdin. When a new line is read from stdin
the callback is called with line read from stdin. You can use keep_running flag to terminate
background thread gracefully.
"""
def thread_fun(cb: Callable[[str], None], flag: threading.Event):
handle = win32api.GetStdHandle(win32api.STD_INPUT_HANDLE)
typ = win32file.GetFileType(handle)
while keep_running.is_set():
# WaitForSingleObject is not supported when stdin is pipe (e.g. when running as subprocess)
if typ == win32file.FILE_TYPE_PIPE:
# alternatively you can specify nonzero parameter to retrieve data in pipe buffer
# and check for LF character before calling input() function
_, available, _ = win32pipe.PeekNamedPipe(handle, 0)
if available > 0:
cb(input())
else:
time.sleep(0.1)
else:
# alternatively you cn use msvcrt.kbhit() with msvcrt.getwche() function, but you
# neet to handle control characters yourself (such as 0x08)
if win32event.WaitForSingleObject(handle, 100) == win32event.WAIT_OBJECT_0:
cb(input())
else:
time.sleep(0.1)
thread = threading.Thread(target=thread_fun, args=(callback, keep_running), daemon=False)
thread.start()
return thread
# hook input
flag = threading.Event()
flag.set()
thr = hook_input(lambda val: print("Received:", val), flag)
# do your stuff
# finish
flag.clear()
thr.join()
You can also handle user input timeout in the callback function. The second implementation is safe to use with asyncio
library, in case you replace time.sleep()
function with asyncio.sleep()
. Note that in this case WaitForSingleObject()
will block event loop from running, therefore I suggest you use combination of short WaitForSingleObject()
with longer asyncio.sleep()
Upvotes: 0
Reputation: 487
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
Upvotes: 1
Reputation: 23637
It's possible to do this with asyncio. Essentially combining https://stackoverflow.com/a/65909044/3005167 and https://stackoverflow.com/a/54787498/3005167 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())
Upvotes: 0
Reputation: 13147
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}'")
Upvotes: 0
Reputation: 520
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
Upvotes: 0
Reputation: 96
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)
Upvotes: 0
Reputation: 418
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}")
Upvotes: 0
Reputation: 108
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)
Upvotes: 2
Reputation: 171
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)
Upvotes: 0
Reputation: 251
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/
Upvotes: 25
Reputation: 325
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()
Upvotes: 0
Reputation: 961
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.
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")
Utilities.user_input(3)
This was made with Python 3.8.3 on Windows 10.
Upvotes: 0
Reputation: 2998
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)
Upvotes: 0
Reputation: 93
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.
Upvotes: 3
Reputation: 41
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
Upvotes: 3
Reputation: 1605
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))
Upvotes: 0
Reputation: 101
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
Upvotes: 5
Reputation: 313
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()
Upvotes: 1
Reputation: 303
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)
Upvotes: 2
Reputation: 514
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
Upvotes: 3
Reputation: 450
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:
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
Upvotes: 4
Reputation: 51
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()
Upvotes: 5
Reputation: 3092
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)
Upvotes: 11
Reputation: 21
Analogous to Locane's for windows:
import subprocess
subprocess.call('timeout /T 30')
Upvotes: 2
Reputation: 3134
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.
Upvotes: 18
Reputation: 4010
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()
Upvotes: 6
Reputation: 41
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 :) )
Upvotes: -3
Reputation: 327
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
Upvotes: 13
Reputation: 1679
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!"
Upvotes: 127
Reputation: 1527
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
Upvotes: 51