1

I'm looking for a solution to time out a user's input after a certain time. This code should print "success..." after 5 seconds without any interaction from the user:

def input_with_timeout(timeout):

    print ("Hello, you can type and press enter to change 'ans' variable value or wait "+str(timeout)+" seconds and the program will continue")   
    ans=input()   #pass input after timeout
    return ans


s="mustnotchange"

s=input_with_timeout(5)

if s=="mustnotchange":
    print("Success, if you didn't just cheat by writing mustnotchange")
else:
    print("you answered : "+s+" variable value has changed")

I know this question was asked often, but NONE solutions provided in the following topics work on WINDOWS 7 and Python 3 (windows 7 "ultimate" SP1 64 bits and Python 3.6.8 64 bits) Python 3 Timed Input

How to set time limit on raw_input

Keyboard input with timeout?

raw_input and timeout

Actually I'm using a "trick" to avoid this problem : I launch another python script with os.startfile() but it can't react to input when started.

It must be very difficult to do it (multiprocessing, threading, queues...) if there isn't any working answers about it, but this can certainly help in a lot of situations.

Thank you.

  • Show how each of those methods don't work for you. I've gotten an least one working on win 10, so having trouble believing nothing at all works. – Mad Physicist Nov 23 '19 at 17:37
  • A lot of them use select() wich work only with linux. Some require a keyboard input so that's not the the point and in the best case like Paul' answer here (https://stackoverflow.com/questions/1335507/keyboard-input-with-timeout) it may seem to work but actually the variable value didn't change at all, it's just a linear execution without interaction. Please give me the author's name and the page if you find out. thank you. – Rémi Descamps Nov 23 '19 at 23:26
  • I've posted a cross-platform solution a while ago here: [Taking in multiple inputs for a fixed time in Python](https://stackoverflow.com/q/53167495/9059420) – Darkonaut Nov 23 '19 at 23:50
  • What about just asking for input in another thread with a timeout and joining that thread? – Mad Physicist Nov 24 '19 at 03:05
  • It's very hard to help you without seeing which options you tried, and detailed explanations of why they didn't work. – Mad Physicist Nov 24 '19 at 03:29
  • I had big hopes in your solution Darkonaut (https://stackoverflow.com/questions/53167495/taking-in-multiple-inputs-for-a-fixed-time-in-python), it's exactly what i want to do but it doesn't work : if a don't press enter, nothing happens.... – Rémi Descamps Nov 24 '19 at 21:32
  • In case I have misunderstood you, the timeout _should_ print before the user has to press Enter of course. If this doesn't work, it might be something with your IDE, try from terminal then. – Darkonaut Nov 25 '19 at 01:26
  • Indeed, when I run your script from IDLE the last message "time is out..." didn't print without pressing ENTER but it works from the terminal ! Now I need to work on it to use it as I want but it's a breakthrough. Thank you. I will post the full solution if I do it. – Rémi Descamps Nov 25 '19 at 13:08
  • You're welcome! You can try adding `flush=True` within your `print()` as it sounds like a buffering issue here with IDLE. But I'd suggest to switch to a more serious IDE, especially if your code is involving concurrency. – Darkonaut Nov 25 '19 at 13:19
  • `flush=True` doesn't change anything but it's not important. Instead of printing the answer if it is in time, I would like to just return a string with that input and stop the "function", which is a class actually but I don't use them so I tried to return `msg` but it's an object. Do you think it is possible ? – Rémi Descamps Nov 25 '19 at 20:52
  • Sounds like you got the `SENTINEL` as `msg` because I defined it as `SENTINEL = object()`. I'm not sure if I understand fully what you mean, though. You want to prompt only once and you don't want to echo what the user typed but `_poll()` should just return the input? – Darkonaut Nov 25 '19 at 21:46
  • This is only a tiny part of my script but yes I want to change variable value if there is a user input in time else the variable doesn't change.My full program is an infinite loop with a speech recognition input but i simplify it with a simple keyboard input because it's the same problem : as long as there isn't any external intervention the code is blocked... So yes in your code I just need to return the input if there is one like `ans=pm.getinput` so i can use ans later and if there isn't input after x seconds the script continue with default value... – Rémi Descamps Nov 26 '19 at 13:16
  • It's possible to boil my code down for just one input, but unless you also prompt the user to at least print Enter (or anything else) on timeout, the prompting thread will be kept alive until your whole program shuts down. The MainThread would still be unblocked and could resume. No idea how this could work with speech-input, though. – Darkonaut Nov 26 '19 at 13:36
  • Then what and where should I put a `return` ? I don't really understand how it works but it's not `rep` because i get `` and i would like a string – Rémi Descamps Nov 26 '19 at 19:20

2 Answers2

1

The code below is for cross-platform prompting with input() once with timeout. On timeout it will ask the user to press Enter so the prompter- thread can shut down cleanly.

Removing the parts affecting _prompter_exit would allow resuming on timeout without the need for the user to hit Enter, at the cost of keeping the prompter-thread alive until the whole process exits.

Just joining a prompting thread with timeout like @Mad Physicist suggested would not work, since it doesn't unblock the input() call itself. Without using OS-specific libraries, there is (AFAIK) no way of waking up a thread waiting on input() without eventually providing some form of input.

The code is a boiled down version of my answer allowing multiple inputs within a given time span:

Taking in multiple inputs for a fixed time in Python

from threading import Thread, enumerate, Event
from queue import Queue, Empty
import time


SENTINEL = None


class PromptManager(Thread):

    def __init__(self, timeout):
        super().__init__()
        self.timeout = timeout
        self._in_queue = Queue()
        self._out_queue = Queue()
        self.prompter = Thread(target=self._prompter, daemon=True)
        self._prompter_exit = Event()

    def run(self):
        """Run worker-thread. Start prompt-thread, fetch passed
        input from in_queue and forward it to `._poll()` in MainThread.
        If timeout occurs before user-input, enqueue SENTINEL to
        unblock `.get()` in `._poll()`.
        """
        self.prompter.start()
        try:
            txt = self._in_queue.get(timeout=self.timeout)
        except Empty:
            self._out_queue.put(SENTINEL)
            print(f"\n[{time.ctime()}] Please press Enter to continue.")
            # without usage of _prompter_exit() and Enter, the
            # prompt-thread would stay alive until the whole program ends
            self._prompter_exit.wait()
        else:
            self._out_queue.put(txt)

    def start(self):
        """Start manager-thread."""
        super().start()
        return self._poll()

    def _prompter(self):
        """Prompting target function for execution in prompter-thread."""
        self._in_queue.put(input(f"[{time.ctime()}] >$ "))
        self._prompter_exit.set()

    def _poll(self):
        """Get forwarded inputs from the manager-thread executing `run()`
        and process them in the parent-thread.
        """
        msg =  self._out_queue.get()
        self.join()
        return msg

For Demonstration:

if __name__ == '__main__':

    pm = PromptManager(timeout=5)
    msg = pm.start()
    print(f"User input: {msg}")

    for i in range(3):
        print(f"[{time.ctime()}] Do something else. "
              f"Alive threads:{[t.name for t in enumerate()]}")
        time.sleep(1)

Run with triggering timeout:

[Tue Nov 26 20:50:47 2019] >$ 
[Tue Nov 26 20:50:52 2019] Please press Enter to continue.

User input: None
[Tue Nov 26 20:50:57 2019] Do something else. Alive threads:['MainThread']
[Tue Nov 26 20:50:58 2019] Do something else. Alive threads:['MainThread']
[Tue Nov 26 20:50:59 2019] Do something else. Alive threads:['MainThread']

Process finished with exit code 0

Run with user input in time:

[Tue Nov 26 20:51:16 2019] >$ Hello
User input: Hello
[Tue Nov 26 20:51:19 2019] Do something else. Alive threads:['MainThread']
[Tue Nov 26 20:51:20 2019] Do something else. Alive threads:['MainThread']
[Tue Nov 26 20:51:21 2019] Do something else. Alive threads:['MainThread']

Process finished with exit code 0
Darkonaut
  • 20,186
  • 7
  • 54
  • 65
1

Finally I modified @Darkonaut answer (Thank you!) to match my first situation and I added a "simulated keyboard" with the library pynput to automatically press "Enter".

Note that this works in Terminal (Python 3.6.8 and Windows 7 SP1) but DOESN'T WORK IF STARTED WITH IDLE.

from threading import Thread, enumerate, Event
from queue import Queue, Empty
import time
from pynput.keyboard import Key, Controller


SENTINEL = None


class PromptManager(Thread):

    def __init__(self, timeout):
        super().__init__()
        self.timeout = timeout
        self._in_queue = Queue()
        self._out_queue = Queue()
        self.prompter = Thread(target=self._prompter, daemon=True)
        self._prompter_exit = Event()

    def run(self):
        """Run worker-thread. Start prompt-thread, fetch passed
        input from in_queue and forward it to `._poll()` in MainThread.
        If timeout occurs before user-input, enqueue SENTINEL to
        unblock `.get()` in `._poll()`.
        """
        self.prompter.start()
        try:
            txt = self._in_queue.get(timeout=self.timeout)
        except Empty:
            self._out_queue.put(SENTINEL)
            print(f"\n[{time.ctime()}] Please press Enter to continue.")
            # without usage of _prompter_exit() and Enter, the
            # prompt-thread would stay alive until the whole program ends
            keyboard = Controller()
            keyboard.press(Key.enter)
            keyboard.release(Key.enter)
            self._prompter_exit.wait()

        else:
            self._out_queue.put(txt)

    def start(self):
        """Start manager-thread."""
        super().start()
        return self._poll()

    def _prompter(self):
        """Prompting target function for execution in prompter-thread."""
        self._in_queue.put(input(f"[{time.ctime()}] >$ "))
        self._prompter_exit.set()

    def _poll(self):
        """Get forwarded inputs from the manager-thread executing `run()`
        and process them in the parent-thread.
        """
        msg =  self._out_queue.get()
        self.join()
        return msg



def input_with_timeout(default, timeout):

    print ("Hello, you can type and press enter to change 'ans' variable value or wait "+str(timeout)+" seconds and the program will continue")   
    pm = PromptManager(timeout)
    ans= pm.start()
    if isinstance(ans, str):
        print("ok")
        return ans
    else:
        return default



s="mustnotchange"

s=input_with_timeout(s,5)

if s=="mustnotchange":
    print("Success, if you didn't just cheat by writing mustnotchange")
else:
    print("you answered : "+s+" variable value has changed")

time.sleep(5)