Interrupting a timer with spacebar - python

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

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.

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)

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()

Related

How do I make the input() function run if no response after a set amount of time? [duplicate]

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
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()

Python: How to resume the python script as soon as vpn network is up?

I have a python script (xyz.py) that I run through the command prompt. My question is that don't we have any method which helps to resume the python code automatically from where it was lost the VPN connection, without any manual intervention. This will help to avoid monitoring the code frequently. Below is my code but it reads from the start if there is any disconnection. Please suggest.
filename = 'xyz.py'
while True:
p = subprocess.Popen('python '+filename, shell=True).wait()
""" #if your there is an error from running 'xyz.py',
the while loop will be repeated,
otherwise the program will break from the loop"""
if p != 0:
continue
else:
break
If me, time.sleep will be used:
import os
import time
from datetime import datetime
import requests
script = 'xyz.py'
def main():
network_check_url = 'http://8.8.8.8'
while True:
try:
requests.get(network_check_url)
except Exception as e:
print(datetime.now(), e)
time.sleep(1)
else:
print('Network is ok. {datetime.now():%Y-%m-%d_%H:%M:%S}')
os.system(f'python {script}')
return
if __name__ == '__main__':
main()

Threading issue in Python 2.7

import time
import thread
import termios
import sys
import datetime
try:
from msvcrt import getch # try to import Windows version
except ImportError:
def getch(): # define non-Windows version
import tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def tm():
char = None
def keypress():
count=0
while count<5:
a=time.time()
global char
char = getch()
b=time.time()
c=b-a
c=c*10000000000000000
c=int (c)
c=c%1000
print c
count+=1
thread.start_new_thread(keypress, ())
while True:
'''if char is not None:
print("Key pressed is " + char.decode('utf-8'))
break'''
print("Program is running")
time.sleep(5)
thread.start_new_thread(tm ())
when I run the code as shown above, it happily does what it's meant to do, which is measure the time in between keystrokes and give the 3 least significant numbers in the measurement.
However, when I take out this part (because I don't need nor necessarily want it there):
while True:
'''if char is not None:
print("Key pressed is " + char.decode('utf-8'))
break'''
print("Program is running")
time.sleep(5)
It breaks. I get the following error code:
Traceback (most recent call last):
File "randgen.py", line 50, in <module>
thread.start_new_thread(tm ())
TypeError: start_new_thread expected at least 2 arguments, got 1
Update
when I add a comma to the thread.start_new_thread(tm, ()) I get this error:
Unhandled exception in thread started by
sys.excepthook is missing
lost sys.stderr
However, when the comma is missing, it runs fine as long as that piece of while True code is there.
You should be using threading instead of thread The thread module is a low-level interface to starting threads, and it's going to require a lot of unnecessary headaches while you're learning this. See this post
Then, the proper way to start a thread would be
import threading
import time
def printstuff(stuff):
while 1:
print(stuff)
time.sleep(3)
t = threading.Thread(target=printstuff, args=('helloworld',))
t.start()
# Here is where your main thread will do whatever
print('Main thread running')
time.sleep(5)
print('Main thread done')
# At this point the main thread will be done
# but printstuff will still be printing stuff.
# Control-c will exit the second thread, and
# youll be back at a prompt
Your code above is not a minimal example, and it's very hard to understand what you're actually trying to accomplish. Try clarifying the goal in some pseudo-code, and then I can work with you to improve your understanding of multi-threading.
Separate Thread
If you truly intend to run tm in a separate thread, you are missing a comma between your two positional arguments. It should read as follows:
thread.start_new_thread(tm, ())
Main Thread
If you do not need the execution of tm to happen in another thread, simple call the function in the current thread like so:
tm()
This is happening already without the comma since the space is removed by the Python interpreter.
thread.start_new_thread(tm ())
# gets turned into
thread.start_new_thread(tm())
# which evaluates tm() before calling 'start_new_thread'
If the loop is removed, the return value of tm() gets passed as a single argument to start_new_thread which results in the original error posted.
Threading
As mentioned in the other answer, you likely should be using the threading library instead of thread. More details about why are listed here.
If you are still using Python 2.7, here is another example.
import thread
import threading
class PeriodicThread(threading._Timer):
def __init__(self, *args, **kwargs):
threading._Timer.__init__(self, *args, **kwargs)
self.daemon = True
def run(self):
while True:
self.finished.clear()
self.finished.wait(self.interval)
if not self.finished.isSet():
self.function(*self.args, **self.kwargs)
else:
return
self.finished.set()
class PeriodicThreadManager(object):
periodic_threads = []
def run(self, interval, callback_fn, args=[], kwargs={}):
new_thread = PeriodicThread(interval, callback_fn, args, kwargs)
self.periodic_threads.append(new_thread)
thread.start_new_thread(new_thread.run, ())
def stop_all(self):
for t in self.periodic_threads:
t.cancel()
self._event.set()
class YourApplication:
def __init__(self):
self.threads = PeriodicThreadManager()
self.threads.run( .01, self.periodic_callback_1 ) # 10 ms but ...
self.threads.run( .01, self.periodic_callback_2 )
def periodic_callback_1(self):
....
def periodic_callback_2(self):
....

Stop an infinite while loop repeatedly invoking os.system

Thank you guys for seeing my post.
First, the following is my code:
import os
print("You can create your own message for alarm.")
user_message = input(">> ")
print("\n<< Sample alarm sound >>")
for time in range(0, 3):
os.system('say ' + user_message) # this code makes sound.
print("\nOkay, The alarm has been set.")
"""
##### My problem is here #####
##### THIS IS NOT STOPPED #####
while True:
try:
os.system('say ' + user_message)
except KeyboardInterrupt:
print("Alarm stopped")
exit(0)
"""
My problem is that Ctrl + C does not work!
I tried changing position of try block, and making signal(SIGINT) catching function.
But those also does not work.
I have seen https://stackoverflow.com/a/8335212/5247212, https://stackoverflow.com/a/32923070/5247212, and other several answers about this problem.
I am using MAC OS(10.12.3) and python 3.5.2.
This is expected behaviour, as os.system() is a thin wrapper around the C function system(). As noted in the man page, the parent process ignores SIGINT during the execution of the command. In order to exit the loop, you have to manually check the exit code of the child process (this is also mentioned in the man page):
import os
import signal
while True:
code = os.system('sleep 1000')
if code == signal.SIGINT:
print('Awakened')
break
However, the preferred (and more pythonic) way to achieve the same result is to use the subprocess module:
import subprocess
while True:
try:
subprocess.run(('sleep', '1000'))
except KeyboardInterrupt:
print('Awakened')
break
Your code would then look like something like this:
import subprocess
print("You can create your own message for alarm.")
user_message = input(">> ")
print("\n<< Sample alarm sound >>")
for time in range(0, 3):
subprocess.run(['say', user_message]) # this code makes sound.
print("\nOkay, The alarm has been set.")
while True:
try:
subprocess.run(['say', user_message])
except KeyBoardInterrupt:
print("Alarm terminated")
exit(0)
As an added note, subprocess.run() is only available in Python 3.5+. You can use subprocess.call() to achieve the same effect in older versions of Python.
Also catch "SystemExit"
except (KeyboardInterrupt, SystemExit):
print("Alarm stopped")
The problem seems to be that Ctrl+C is captured by the subprocess you call via os.system. This subprocess reacts correspondingly, probably by terminating whatever it is doing. If so, the return value of os.system() will be not zero. You can use that to break the while loop.
Here's an example that works with me (substituting say by sleep):
import os
import sys
while True:
try:
if os.system('sleep 1 '):
raise KeyboardInterrupt
except KeyboardInterrupt:
print("Alarm stopped")
sys.exit(0)
If Ctrl-C is captured by the subprocess, which is the case here, the simplest solution is to check the return value of os.system(). For example in my case it returns value of 2 if Ctrl-C stops it, which is a SIGINT code.
import os
while True:
r = os.system(my_job)
if r == 2:
print('Stopped')
break
elif r != 0:
print('Some other error', r)

Callbacks with Python curses

I have code that looks like this:
stdscr.nodelay(1)
while True:
c = stdscr.getch()
if c != -1:
stdscr.addstr("%s was pressed\n" % c)
if time() - last > 1:
stdscr.addstr("time() == %s\n" % str(time()))
last = time()
However, I'm worried that my code is really wasteful/inefficient. Is there a callback mechanism available for curses? If not, what would be the canonical way to handle this kind of situation?
You could try Urwid instead.
Urwid provides a higher-level toolkit on top of curses and includes an event loop to handle keyboard and mouse input. It either uses it's own select-based event loop, or can hook into gevent or Twisted.
In addition to handling keyboard input efficiently you'll also have a host of options to handle user input with edit boxes, list controls and more.
How about using half-delay mode to make getch() blocking?
import time
import curses
stdscr = curses.initscr()
curses.halfdelay(10) # 10/10 = 1[s] inteval
try:
while True:
c = stdscr.getch()
if c != -1:
stdscr.addstr("%s was pressed\n" % c)
stdscr.addstr("time() == %s\n" % time.time())
finally:
curses.endwin()

Categories