Making An “Any Key” Interuptable Python Timer

2019-02-26 04:51发布

问题:

I am trying to make a simple timer which counts up until it is interrupted by keyboard input.

right now I am using CTRL+C to stop the timer, but I would like to do something more simple like hitting space or enter or "any key". I hear this can be done with the threading module, but after several attempts I clearly do not know what I am doing with that.

this is my current code:

def countup():
    try:
        a=0
        for i in range(1000000) :
            print i,'\r',
            time.sleep(1)
    except KeyboardInterrupt:
         Z = raw_input("restart timer?" )
         if Z == "Y" or Z == "y" :
             countup()

回答1:

Using thread and terminal capabilities you can write (press any key to stop):

import thread
import time

def read_key():
    import termios
    import sys
    fd = sys.stdin.fileno()
    old = termios.tcgetattr(fd)
    new = termios.tcgetattr(fd)
    new[3] &= ~(termios.ICANON | termios.ECHO) # c_lflags
    c = None
    try:
        termios.tcsetattr(fd, termios.TCSANOW, new)
        c = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSANOW, old)
    return c

def input_thread():
    read_key()
    thread.interrupt_main()

def countup():
    try:
        thread.start_new_thread(input_thread, ())
        for i in range(1000000):
            print i
            time.sleep(1)
    except KeyboardInterrupt:
        Z = raw_input("restart timer? ")
        if Z == 'y' or Z == 'Y':
            countup()

Let's clarify a bit:

thread.start_new_thread() create a new thread using input_thread() as start function. While thread.interrupt_main() raise KeyboardInterrupt in the main thread.

termios.tcgetattr() return the current terminal attribute. ~termios.ICANON unset the canonical mode and ~termios.ECHO prevent input print then termios.tsetattr() act the change.

Alternatively, on Windows, getch() from msvcrt can be use in place of read_key()

def input_thread():
    msvcrt.getch()
    thread.interrupt_main()

Reference

  • Thread module
  • Termios module
  • Msvcrt module


回答2:

There are two problems with your code as it stands:

  1. You aren't checking for keyboard input during the for loop, so the program isn't going to notice if you type random characters to stdin.
  2. KeyboardInterrupt exceptions are thrown specifically when the interrupt key (i.e. Ctrl-C) is pressed.

The problem is that you need to attempt to read from stdin, but not wait until input appears there if there isn't any to begin with. This page provides a good description of how to do this.

Based on the link above, this code should do the trick to make the countup stop if you hit enter:

import sys 
import select 
import time

def countup():
    i = 0
    while True:
        print i
        i += 1
        time.sleep(1)
        while sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
            line = sys.stdin.readline()
            if line:
                r = raw_input("Restart?")
                if r.lower() == "y":
                    countdown()
                else:
                    break

 countup()