11

I'm trying to find a way to shorten a time.sleep(600) if the user inputs a key, without resorting to some ugly hack like:

key_pressed = False
for i in range(600):
    key_pressed = key_was_pressed()
    if not key_pressed:
        time.sleep(1)
    else:
        break
François M.
  • 4,027
  • 11
  • 30
  • 81
  • have you considered a listener thread + notify? – Gulzar May 12 '20 at 18:34
  • I'll settle for any key (except CTRL-C) but if I can set the key(s), it's even better. – François M. May 12 '20 at 18:54
  • is it safe to assume Python3? Is the platform (Win/*nix) known in advance? Is this project by chance using tornado? – Marat May 19 '20 at 00:19
  • Very similar (same?) question was asked in https://stackoverflow.com/questions/6179537/python-wait-x-secs-for-a-key-and-continue-execution-if-not-pressed – d99kris May 19 '20 at 00:20
  • 1
    Yup, Python3. Windows, but a general solution is always better. – François M. May 19 '20 at 00:20
  • 1
    @d99kris The accepted answer involves busy-waiting, precisely what I want to avoid, as you can see in the ugly hack in my question. (I know `time.sleep()` is technically also busy-waiting, but much less ressource-intensive than a `while True:` loop) – François M. May 19 '20 at 00:23
  • Also check this answer https://stackoverflow.com/a/43065186/9504749 – dumbPy May 19 '20 at 00:25

1 Answers1

11

This is a cross-platform adaptation of an implementation using signal.alarm interrupt (an idea which is not available on Windows). This code should work for Linux, macOS, and Windows. The 3rd-party helper library readchar can be installed with pip install readchar.

import os
import signal
import sys

from threading import Timer
from readchar import readkey

def wait_for(key="x", timeout=600):
    pid = os.getpid()
    sig = signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT
    timer = Timer(timeout, lambda: os.kill(pid, sig))
    print(f"waiting {timeout}s for user to press {key!r} ...")
    timer.start()  # spawn a worker thread to interrupt us later
    while True:
        k = readkey()
        print(f"received {k!r}")
        if k == key:
            timer.cancel()  # cancel the timer
            print("breaking")
            break

def main():
    import sys
    try:
        wait_for(key=sys.argv[1], timeout=int(sys.argv[2]))
    except KeyboardInterrupt as err:
        print("user took too long")

if __name__ == "__main__":
    main()
wim
  • 338,267
  • 99
  • 616
  • 750
  • Won't this flood one core with the `while True` loop? – Robin De Schepper May 19 '20 at 00:26
  • 4
    No. getch is blocking. – wim May 19 '20 at 00:26
  • `readchar.readkey()` doesn't seem to work on Spyder, but if I simulate the user pressing `x`, it works. However, if I do nothing, it doesn't work and I get : `Restarting kernel Bad key "text.kerning_factor" on line 4 in C:\Users\Francois\Anaconda3\lib\site-packages\matplotlib\mpl-data\stylelib\_classic_test_patch.mplstyle. You probably need to get an updated matplotlibrc file from https://github.com/matplotlib/matplotlib/blob/v3.1.3/matplotlibrc.template or from the matplotlib source distribution` and after that, retrying in the same console crashes it. – François M. May 25 '20 at 04:48
  • @FrançoisM. I am not familiar with spyder. Does this code work directly in Python on your platform? – wim May 25 '20 at 04:54