I'm trying to create a looping python function which performs a task and prompts the user for a response and if the user does not respond in the given time the sequence will repeat.
This is loosely based off this question: How to set time limit on raw_input
The task is represented by some_function()
. The timeout is a variable in seconds. I have two problems with the following code:
The raw_input prompt does not timeout after the specified time of 4 seconds regardless of whether the user prompts or not.
When raw_input of 'q' is entered (without '' because I know anything typed is automatically entered as a string) the function does not exit the loop.
`
import thread
import threading
from time import sleep
def raw_input_with_timeout():
prompt = "Hello is it me you're looking for?"
timeout = 4
astring = None
some_function()
timer = threading.Timer(timeout, thread.interrupt_main)
try:
timer.start()
astring = raw_input(prompt)
except KeyboardInterrupt:
pass
timer.cancel()
if astring.lower() != 'q':
raw_input_with_timeout()
else:
print "goodbye"
`
What if instead of calling
some_function
when the input times out, you turn that into a background thread that keeps going with an interval of the timeout? The work will keep going while the main thread is permanently blocked on waiting for input. You may decide to react differently to that input based on what the worker is doing (working or sleeping) - you might just completely ignore it. AFAIK, there is no noticeable difference between not taking input or taking input but ignoring it. This idea leverages that.Note: All I intend to do is to demonstrate another way of thinking about the problem that may or may not be appropriate in your particular case. I do think it is very flexible though.
Proof of concept:
It doesn't rely on anything platform specific; it's just simple Python code. Only after trying some alternative implementations taking input from within a thread, I realized how much of an advantage leaving user input to the main thread is.
I didn't pay too much attention to making it safe and clean, but for sure it can be done while keeping the overall structure. The biggest flaw I can see with it is that earlier input is never going away. It causes confusion when the worker outputs, obscuring the earlier input. If you press
q
but notEnter
in time, pressingq
andEnter
next time results in the input ofqq
even when thoseq
s are not next to each other on the screen. Generally this is how command line applications work, so I'm not sure if it's worth fixing. You might consider accepting input consisting only ofq
s as cancelation as well. Another option would be to read fromstdin
directly, not usingraw_input
.Some idea to make the structure of the code nicer would be to make the main thread even dumber and have it pass all input to the worker (using a queue) to decide what to do with.
I do not think that there is a way to show a prompt that will expire after time passes without displaying a different message from another thread.
You can add the following line before the call to raw_input:
You can define the
interrupt_user
function as follows:In the
raw_input_with_time
function, do not call sleep. Instead, save the time from before the call to raw_input, and determine if the elapsed time after the call is more than 5 seconds. Also, if the user entered 'q' then it should not call itself so the loop will stop.Warning: This is intended to work in *nix and OSX as requested but definitely will not work in Windows.
I've used this modification of an ActiveState recipe as a basis for the code below. It's an easy-to-use object that can read input with a timeout. It uses polling to collect characters one at a time and emulate the behavior of
raw_input()
/input()
.Input with Timeout
Note: apparently the
_getch_nix()
method below doesn't work for OP but it does for me on OSX 10.9.5. You might have luck calling_getch_osx()
instead although it seems to work in 32-bit python only since Carbon doesn't fully support 64-bit.Test it
Repeated Input
This implements your original intention as I understand it. I don't see any value to making recursive calls - I think what you want is just to get input repeatedly? Please correct me if that is wrong.
You can set an alarm before input and then bind the alarm to a custom handler. after the given period alarms goes off, handler raises an exception, and your custom
input
function may handle the rest.a quick example:
Credit where it is due: Keyboard input with timeout in Python
This is just prof of concept. Asking user for input data.
Another way of doing it is to place the IO blocking in the new thread (as opposed to your proposed scheme where you have it in your main thread). The caveat for this is that there is not a clean way of killing a thread in python, so this does not play nice with repeating calls (N threads will hang around until main ends, and I think raw_input does not play nice...).
So, be warned, this works once, far from perfect solution