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()
import sys
import threading
import tty
import termios
def loop():
fd = sys.stdin.fileno()
mode = termios.tcgetattr(fd)
tty.setraw(fd)
try:
while True:
sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, mode)
threading.Thread(target=loop).start()
I tried to manually send End-Of-Transmission character using sys.stdin.write(chr(4)) but it results in io.UnsupportedOperation: not writable.
I tried to close stdin but it will not terminate blocking read(1) method immediately. It triggers an exception after I typed another key.
I tried seletors but selector.select() need extra keys after I invoked selector.close(). And selector it self needs an event loop and this loop becomes uninterruptable.
If you run the loop in a thread then it will be hard to interrupt and also keep original process running. But it's easy to run this loop in a subprocess. So you can interrupt the subprocess to interrupt the blocking stdin.read(1)
import os
import sys
import tty
import termios
import time
import signal
from multiprocessing import Process
def loop():
# https://stackoverflow.com/questions/30134297/python-multiprocessing-stdin-input
# Subprocess stdin will be set to os.devnull. So we need to reopen it here.
# Use parent process stdin(fd=0) instead of subprocess stdin(fd=7)
sys.stdin = open(0) #
fd = sys.stdin.fileno()
mode = termios.tcgetattr(fd)
tty.setraw(fd)
try:
while True:
ch = sys.stdin.read(1)
if ord(ch) == 3:
break
except KeyboardInterrupt:
print("interrupted!\r\n", end="")
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, mode)
if __name__ == '__main__':
p = Process(target=loop)
p.start()
time.sleep(1)
os.kill(p.pid, signal.SIGINT)
p.join()
print("done")
At the moment I'm using AutoHotKey for triggering scripts via keyboard shortcuts. I like programming in Python much more than dealing with AutoHotKey and every time I touch my AutoHotKey scripts I wish I could simply write clean AutoHotkey code.
Let's take the simply AutoHotKey script that prints hello world in whatever window I am when I press the insert key:
foo(){
send, "hello world"
}
Insert:: foo()
How would I do the same thing in Python3 on Windows?
You will have to hook into the gizzards of windows to achieve this. You'd probably have to do that via the ctypes or CFFI module, because the necessary API's don't seem to exist in pywin32.
According to this page, you will need to use three windows API calls:
SetWindowsHookEx, using the idHook WH_KEYBOARD_LL to set up a keyboard hook; a function that peeks at keyboard events.
UnhookWindowsHookEx to eventually remove the hook.
And if you're not interested in a particular keypress, CallNextHookEx to pass it on to the next hook.
You can combine two answers from StackOverflow to (almost) solve this issue.
Use this answer (by tehvan) to create a getch() like method to read in one character from the user without the need for a \n. (repeated below from the answer)
Use the Python3 version of this answer (by Barafu Albino) to call the previously defined _Getch() class in a separate process.
Please note that the following code works for Python3 only and uses any key to stop the process, not just the insert key.
# This code is a combination of two StackOverflow answers
# (links given in the answer)
# ------- Answer 1 by tehvan -----------------------------------
class _Getch:
"""Gets a single character from standard input.
Does not echo to the screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, 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
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
getch = _Getch()
# -------- Answer 2 by Barafu Albino (modified) -------
import _thread
def input_thread(a_list):
_Getch().__call__()
a_list.append(True)
def do_stuff():
a_list = []
_thread.start_new_thread(input_thread, (a_list,))
print('Press any key to stop.')
while not a_list:
pass
# This is where you can put the stuff
# you want to do until the key is pressed
print('Stopped.')
do_stuff()
I have an infinite loop in which there are operations that are mandatory to be completely executed before exiting the loop. Namely, I am using the socket library for connecting to an external device and I need to wait the read instructions to be finished before interrupting the loop.
I have tried using a signal handler (like in this question) for raising a flag when a Keyboard interrupt is detected.
Current code:
import videosensor
import signal
def signal_handler(signal, frame):
"""Raises a flag when a keyboard interrupt is raised."""
global interrupted
interrupted = True
if __name__ == '__main__':
camera = videosensor.VideoSensor(filename)
interrupted = False
signal.signal(signal.SIGINT, signal_handler)
while not interrupted:
location = camera.get_register()
#...
#More irrelevant stuff is executed.
#...
time.sleep(0.01)
#This code has to be executed after exiting while loop
camera_shutdown(camera)
In the previous code, videosensor.VideoSensor is a class containing socket operations for getting data from an external device. The get_register() method used in the main routine is the following:
def get_register(self):
"""Read the content of the specified register.
"""
#Do some stuff
value = socket.recv(2048)
return value
The problem:
I wanted the while loop to be continually executed until the user pressed a key or used the Keyboard Interrupt, but after the current iteration was finished. Instead, using the previous solution does not work as desired, as it interrupts the ongoing instruction, and if it is reading the socket, an error is raised:
/home/.../client.pyc
in read_register(self, regkey)
164 reg = self._REGISTERS[regkey]
165 self.send('r,{}\n'.format(reg))
--> 166 value = socket.recv(2048)
167 #Convert the string input into a valid value e.g. list or int
168 formatted_result = ast.literal_eval(value)
error: [Errno 4] Interrupted system
EDIT: It seems, from an answer below, that there is no way of using the Keyboard Interrupt and avoid the socket read function to be aborted. Despite there are solutions for catching the error, they don't avoid the read cancellation.
I am interested, though, in finding a way of getting a user input e.g. specific key press, that raises the flag, which will be checked at the end of the loop, without interrupting the main routine execution until this check.
EDIT2: The used OS is the Linux distribution Ubuntu 14.04
After quick SO search I found this solution for your issue
Basically, there's nothing you can do: when you send a SIGINT to your process, the socket will return a SIGINT as well. The best you can do, then, is to actively ignore the issue, by catching the socket EINTR error and going on with your loop:
import errno
try:
# do something
value = conn.recv(2048)
except socket.error as (code, msg):
if code != errno.EINTR:
raise
An alternative solution to avoid issues with C-c breaking reads, is to use parallel execution, to read your socket in a routine, and handle user input on the other:
import asyncio
async def camera_task(has_ended, filename):
camera = videosensor.VideoSensor(filename)
try:
while not has_ended.is_set():
location = camera.get_register()
#...
#More irrelevant stuff is executed.
#...
await asyncio.sleep(0.01)
finally:
#This code has to be executed after exiting while loop
camera_shutdown(camera)
async def input_task(shall_end):
while True:
i = input("Press 'q' to stop the script…")
if i == 'q':
shall_end.set()
def main():
filename = …
#
end_event = asyncio.Event()
asyncio.Task(camera_task(end_event, filename))
asyncio.Task(input_task(end_event))
asyncio.get_event_loop().run_forever()
or with threading
import threading, time
def camera_task(has_ended, filename):
camera = videosensor.VideoSensor(filename)
try:
while not has_ended.is_set():
location = camera.get_register()
#...
#More irrelevant stuff is executed.
#...
time.sleep(0.01)
finally:
#This code has to be executed after exiting while loop
camera_shutdown(camera)
def input_task(shall_end):
while True:
i = input("Press 'q' to stop the script…")
if i == 'q':
shall_end.set()
def main():
filename = …
#
end_event = threading.Event()
threads = [
threading.Thread(target=camera_task, args=(end_event, filename)),
threading.Thread(target=input_task, args=(end_event,))
]
# start threads
for thread in threads:
thread.start()
# wait for them to end
for thread in threads:
thread.join()
or with multiprocessing:
import multiprocessing, time
def camera_task(has_ended, filename):
camera = videosensor.VideoSensor(filename)
try:
while not has_ended.is_set():
location = camera.get_register()
#...
#More irrelevant stuff is executed.
#...
time.sleep(0.01)
finally:
#This code has to be executed after exiting while loop
camera_shutdown(camera)
def input_task(shall_end):
while True:
i = input("Press 'q' to stop the script…")
if i == 'q':
shall_end.set()
def main():
filename = …
#
end_event = multiprocessing.Event()
processes = [
multiprocessing.Process(target=camera_task, args=(end_event, filename)),
multiprocessing.Process(target=input_task, args=(end_event,))
]
# start processes
for process in processes:
process.start()
# wait for them to end
for process in processes:
process.join()
disclaimer: those codes are untested, and there might be some typos or little errors, but I believe the overall logic should be 👌
You created your custom signal handler but did not overide the default keyboard interrupt behaviour. Add signal.signal(signal.SIGINT, signal_handler) to your code to accomplish this:
import videosensor
import signal
# Custom signal handler
def signal_handler(signal, frame):
"""Raises a flag when a keyboard interrupt is raised."""
global interrupted
interrupted = True
# Necessary to override default keyboard interrupt
signal.signal(signal.SIGINT, signal_handler)
if __name__ == '__main__':
# Main programme
If I understand correctly, you do not want socket.recv() to be interrupted, but you do want to use signals to let the user indicate that the I/O loop should be terminated once the current I/O operation has completed.
With the assumption that you are using Python 2 on a Unix system, you can solve your problem by calling signal.siginterrupt(signal.SIGINT, False) before entering the loop. This will cause system calls to be restarted when a signal occurs rather than interrupting it and raising an exception.
In your case this means that the socket.recv() operation will be restarted after your signal handler is called and therefore get_register() will not return until a message is received on the socket. If that is what you want your code will be:
interrupted = False
old_handler = signal.signal(signal.SIGINT, signal_handler) # install signal handler
signal.siginterrupt(signal.SIGINT, False) # do not interrupt system calls
while not interrupted:
location = camera.get_register()
if location == '':
# remote connection closed
break
#...
#More irrelevant stuff is executed.
#...
time.sleep(0.01)
That's one way to do it, but it does require that your code is running on a Unix platform.
Another way, which might work on other platforms, is to handle the exception, ignore further SIGINT signals (in case the user hits interrupt again), and then perform a final socket.recv() before returning from the get_register() function:
import errno
def get_register(s):
"""Read the content of the specified register.
"""
#Do some stuff
try:
old_handler = None
return s.recv(2048)
except socket.error as exc:
if exc.errno == errno.EINTR:
old_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) # ignore this signal
return s.recv(2048) # system call was interrupted, restart it
else:
raise
finally:
if old_handler is not None:
signal.signal(signal.SIGINT, old_handler) # restore handler
Signal handling can get tricky and there might be race conditions in the above that I am not aware of. Try to use siginterrupt() if possible.
I have been trying to write a keyboard listener without installing any packages. What I wanted was to create a non-blocking way of reading only one character of user input. So I created another thread besides the main one. Here is my code:
import sys, os
import thread
import time
try:
from msvcrt import getch
except ImportError:
def getch():
import sys, 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
char = None;
def key_listener():
global char;
while True:
char = getch()
# escape key to exit
if ord(char) == 27:
break
#print char #comment this line for test
thread.start_new_thread(key_listener, ())
while True:
print("Whatever")
time.sleep(1);
And the printed strings are a bit weird:
Yinans-MacBook-Pro:anaconda game Yinan$ python inputtest.py
Whatever
Whatever
Whatever
Whatever
Whatever
Whatever
See those indents? I never expected to have that. And I have been trying a whole afternoon to solve it but failed. Does anybody know how to solve this? I will be so grateful. (btw I'm using a macbook pro.)
Putting STDIN in raw mode put STDOUT in raw mode as well, so the normal \n is not expanded to a CRLF. You will need to print a \r at the end of your string in order to return the cursor to the first column.