33

What I would like to be able to do is ask a user a question using input. For example:

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

and then if the time elapses print something like

print('Sorry, times up.')

Any help pointing me in the right direction would be greatly appreciated.

cloud311
  • 2,381
  • 4
  • 18
  • 20
  • 1
    @interjay I already read this posting before posting my question. First of all, I'm on a windows platform not Unix. The accepted answer says it is Unix only, and I believe the person who answered it later said it didn't even work. Also I'm working with Python 3. I need to use input not raw_input. – cloud311 Mar 20 '13 at 16:47
  • 3
    There are multiple answers on that question and on the one posted by Francesco Frassinelli, many of which are not unix-only. And you can simply change `raw_input` to `input`. BTW When you ask questions you should specify relevant information such as running on Windows, and solutions you have tried but didn't work so that people don't waste their time rewriting the old answers. – interjay Mar 20 '13 at 16:52
  • 1
    [Keyboard input with timeout in Python](http://stackoverflow.com/q/1335507/4279) – jfs Oct 20 '14 at 02:45
  • related: [raw_input and timeout /3471461](https://stackoverflow.com/questions/3471461/raw-input-and-timeout) – n611x007 Mar 13 '15 at 13:13
  • related: [How to set time limit on input /2933399](https://stackoverflow.com/questions/2933399/how-to-set-time-limit-on-input) – n611x007 Mar 13 '15 at 13:13
  • this worked for me https://stackoverflow.com/a/2904057/2374691 – Ritwik Dec 20 '19 at 08:42

2 Answers2

26

If it is acceptable to block the main thread when user haven't provided an answer:

from threading import Timer

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

Otherwise, you could use @Alex Martelli's answer (modified for Python 3) on Windows (not tested):

import msvcrt
import time

class TimeoutExpired(Exception):
    pass

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

Usage:

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

On Unix you could try:

import select
import sys

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

Or:

import signal

def alarm_handler(signum, frame):
    raise TimeoutExpired

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

    try:
        return input(prompt)
    finally:
        signal.alarm(0) # cancel alarm
DannyTalent
  • 128
  • 5
jfs
  • 346,887
  • 152
  • 868
  • 1,518
  • 3
    First answer did print after a timeout, but the input was still available. – Eliezer Miron Jul 12 '16 at 22:57
  • 1
    @EliezerMiron: yes, the `input()` call is not interrupted in the first example that is why there is: *"If it is acceptable to block the main thread"* before the example. If you need to interrtupt the input, use the following examples with `input_with_timeout()`. – jfs Sep 30 '16 at 20:00
  • I tried using the `import signal` one, and its sense of timing appears to be way off. I'm using Cloud9 IDE. If I give it a .5 timeout, it will wait about 3 seconds before timing out. – Pro Q Feb 03 '17 at 05:16
  • For the windows option, you may want to replace `if result[-1] == '\n':` for `if result[-1] == '\r':` (tested on vscode, pwshell, cmd and terminal app) – DannyTalent Feb 02 '21 at 18:12
  • 1
    @DannyTalent Done. – jfs Feb 03 '21 at 14:06
12

Interesting problem, this seems to work:

import time
from threading import Thread

answer = None

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

Thread(target = check).start()

answer = input("Input something: ")
mediocrity
  • 381
  • 3
  • 5