Skip to content Skip to sidebar Skip to footer

Timed Input With Python 3 And Windows 7

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_wit

Solution 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

Solution 2:

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)

Post a Comment for "Timed Input With Python 3 And Windows 7"