2

Say I want to time how long I can hold my breath, and I want to do that with Python. I have short script:

start = time()
try:
    while True: pass
except KeyboardInterrupt:
    print(time() - start)

This has the basic functionality I want, but it has an fatal shortcoming. After a long period of holding my breath, my mind might be a little fuzzy, and I might not find the coordination to hit Ctrl+c right away, and I might loose important data about my training.

The spacebar is much easier target to hit. Is there a simple way to make the loop stop when I press it?

EDIT: I'm on OSX

Andrea
  • 302
  • 2
  • 6
  • 12
  • 2
    Lol... seriously? Just use a stopwatch. – cs95 Aug 12 '17 at 17:22
  • fatal shortcoming.... Unlikely, you will pass out prior to dying. You will only have a fatal shortcoming if you are doing it under water. – Fallenreaper Aug 12 '17 at 17:30
  • @cᴏʟᴅsᴘᴇᴇᴅ not seriously, but I would really like to be able to interrupt a loop with space bar rather than ctrl c – Andrea Aug 12 '17 at 19:07

3 Answers3

1

You need to put the console's keyboard (pty) driver into raw mode. It is explained in this answer: What is the easiest way to detect key presses in python 3 on a linux machine?

Quoting liberally from that answer:

#! /usr/bin/env python3
import sys
import termios
import time
import tty


def hold_breath(fin):
    orig_setting = termios.tcgetattr(fin)
    tty.setraw(fin)
    start = time.time()
    try:
        ch = fin.read(1)[0]
        assert ch == ' '
    finally:
        print('You lasted %.03f seconds.\r' % (time.time() - start))
        termios.tcsetattr(fin, termios.TCSADRAIN, orig_setting)


if __name__ == '__main__':
    print('Hit space.')
    hold_breath(sys.stdin)

Works fine on OS/X and Linux. If you wind up interrupting the program before it restores the original setting, then $ stty sane is your friend.

J_H
  • 17,926
  • 4
  • 24
  • 44
  • This looks promising, but link only answers should not be accepted on SE. – Andrea Aug 12 '17 at 22:46
  • What does fin stand for, and why wouldn't I just use sys.stdin whenever needed when defining hold_breath? – Andrea Aug 13 '17 at 14:29
  • Also, this solution does not allow me to have anything in the loop, as sys.stdin.read(1) freezes until a key is pressed. I cannot display a timer, say. – Andrea Aug 13 '17 at 14:54
  • If you like sys.stdin, that's cool, stick with that. I commonly use fout & fin as file output and file input identifiers, and fin is just shorter here. – J_H Aug 16 '17 at 14:31
  • For non-blocking I/O, see https://stackoverflow.com/questions/292095/polling-the-keyboard-detect-a-keypress-in-python – J_H Aug 16 '17 at 14:32
1

The answer depends on your OS. On Windows, this will stop on any key press, but you could look at the return value of msvcrt.getch() to determine if it was space. Now when you pass out and your face hits the keyboard it will stop on any key.

import time
import msvcrt
start = time.time()
while not msvcrt.kbhit():  # Indicates a key is waiting to be read
    pass
end = time.time()
msvcrt.getch()  # read and (in this case) throw away the key press.
print(end-start)
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • This looks exactly like what I want... too bad I'm on MacOS. I should have specified in advance. – Andrea Aug 12 '17 at 22:39
0
import sys                                                                          
import termios                                                                      
import contextlib                                                                   

SPACE_BAR = 32                                                                      

@contextlib.contextmanager                                                       
def raw_mode(file):                                                                 
    old_attrs = termios.tcgetattr(file.fileno())                                    
    new_attrs = old_attrs[:]                                                        
    new_attrs[3] = new_attrs[3] & ~(termios.ECHO | termios.ICANON)                  
    try:                                                                            
        termios.tcsetattr(file.fileno(), termios.TCSADRAIN, new_attrs)              
        yield                                                                       
    finally:                                                                        
        termios.tcsetattr(file.fileno(), termios.TCSADRAIN, old_attrs)              


def main():                                                                         
    print 'exit with spacebar'                                                      
    with raw_mode(sys.stdin):                                                       
        try:                                                                        
            while True:                                                          
                if sys.stdin.read(1) == chr(SPACE_BAR):                          
                    break                                                        

        except (KeyboardInterrupt, EOFError):                                    
            pass                                                                 


if __name__ == '__main__':                                                       
    main()                 
gushitong
  • 1,898
  • 16
  • 24