I just started learning Raspberry Pi development with python and have a simple RGB LED circuit setup on my breadboard, I then connected this to Pubnub to control it from a little web interface I built which just sends a method name and RGB values over Pubnub where the python script has subscribed to the particular channel.
from pubnub import Pubnub
import RPi.GPIO as G
import time
pubnub = Pubnub(publish_key="****", subscribe_key="****")
def callback(message, channel):
globals()[message['method']](message['data'])
def error(message):
print("ERROR: " + str(message))
def connect(message):
print("CONNECTED")
def reconnect(message):
print("RECONNECTED")
def disconnect(message):
print("DISCONNECTED")
G.setmode(G.BCM)
red_channel_pin = 18
green_channel_pin = 23
blue_channel_pin = 24
G.setup(red_channel_pin, G.OUT)
G.setup(green_channel_pin, G.OUT)
G.setup(blue_channel_pin, G.OUT)
pwm_red = G.PWM(red_channel_pin,500)
pwm_red.start(100)
pwm_green = G.PWM(green_channel_pin,500)
pwm_green.start(100)
pwm_blue = G.PWM(blue_channel_pin,500)
pwm_blue.start(100)
def set_rgb_values(data):
pwm_red.ChangeDutyCycle(float(data['red']))
pwm_green.ChangeDutyCycle(float(data['green']))
pwm_blue.ChangeDutyCycle(float(data['blue']))
try:
pubnub.subscribe(channels="rasprgb",callback=callback, error=error, connect=connect, reconnect=reconnect, disconnect=disconnect)
except KeyboardInterrupt:
print('Cleaning Up')
G.cleanup()
pubnub.unsubscribe(channel='rasprgb')
All of this works except for trying to close the program and clean up the GPIO pins, unsubscribe from the channel, etc.
In the past I've used a while True: loop and that has worked, but since I don't want to do something in a loop here, I just want to open a connection and leave it open until I terminate it a loop doesn't make sense here
Hitting Ctrl + C just outputs KeyboardInterrupt but it doesn't seem to be calling the except block
How can I get this to be able to terminate and clean up GPIO pins?
UPDATE
After refactoring to use signal here is what I have now replacing the try...except (assuming I have imported them at the top of the file)
def sig_handler(signal,frame):
print('Cleaning Up')
G.cleanup()
pubnub.unsubscribe(channel='rasprgb')
sys.exit(0)
pubnub.subscribe(channels="rasprgb",callback=callback, error=error, connect=connect, reconnect=reconnect, disconnect=disconnect)
signal.signal(signal.SIGINT, sig_handler)
However, pressing ctrl + c still does not close out the program and run the cleanup code
Using the signal module, you can create a global interrupt handler:
import signal
import sys
def sig_handler(signal, frame):
print('Cleaning Up')
G.cleanup()
pubnub.unsubscribe(channel='rasprgb')
sys.exit(0)
signal.signal(signal.SIGINT, sig_handler)
Now, when you CTRL-C, your cleanup code will run, and the program will exit. I use Perl mostly on my Pis, but I do the same thing to ensure that all pins are reset before a re-run of the same application, or a run of a different app.
You can exit a script using any key press with this function. I also don't see a problem with a while loop but if I really needed an alternative method, I would use this:
import sys, os
def wait_key():
''' Wait for a key press on the console and return it. '''
result = None
import termios
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
try:
result = sys.stdin.read(1)
except IOError:
pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
return result
print('Press any key to quit: ')
wait_key()
Another similar method can be utilized by using the curses module like so:
import curses
stdscr = curses.initscr()
c = stdscr.getch()
print 'you entered', chr(c)
curses.endwin()
Also if you are using Windows, you can use the msvcrt module:
import msvcrt
c = msvcrt.getch()
print 'you entered', c
Related
I want to write a python program (run.py) that always runs and only stops running when Ctr-C is pressed. This is how I implement it:
wrapper.py:
import subprocess
import signal
def sig_handler(signum, frame):
res = input("Ctrl-c was pressed. Do you really want to exit? y/n ")
if res == 'y':
exit(1)
signal.signal(signal.SIGINT, sig_handler)
while(True):
p = None
p = subprocess.Popen("python run.py", shell=True)
stdout, stderr = p.communicate()
run.py:
print('aaaaa')
print('bbbbb')
However, when I hold left-mouse and select text in the terminal that is running wrapper.py, this event is understood incorrectly as Ctr-C then the wrapper.py stop running run.py. My question is how to prevent reading mouse events as KeyboardInterrupt in python (Unix). Thanks!
Terminal
Instead of using a module like signal to achieve this you could opt to use exceptions since it is a pretty exceptional case that your program will receive a keyboard interrupt.
#!/usr/bin/env python3
import sys
def main() -> int:
try:
while True:
print(sys.argv)
except KeyboardInterrupt as e:
return 0
if __name__ == '__main__':
try:
sys.exit(main())
except Exception as e:
print(f'Error: {e}', file=sys.stderr)
sys.exit(1)
The source code has no problem. The problem is caused by dictionary software. The software has a feature of selecting word to translate. By somehow it converts the mouse event (selecting word) to Ctr-C then the program above exits. When I turn off the dictionary software, the problem disappears. I will close the thread here
I have an infinite while loop that I want to break out of when the user presses a key. Usually I use raw_input to get the user's response; however, I need raw_input to not wait for the response. I want something like this:
print 'Press enter to continue.'
while True:
# Do stuff
#
# User pressed enter, break out of loop
This should be a simple, but I can't seem to figure it out. I'm leaning towards a solution using threading, but I would rather not have to do that. How can I accomplish this?
You can use non-blocking read from stdin:
import sys
import os
import fcntl
import time
fl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, fl | os.O_NONBLOCK)
while True:
print("Waiting for user input")
try:
stdin = sys.stdin.read()
if "\n" in stdin or "\r" in stdin:
break
except IOError:
pass
time.sleep(1)
I think you can do better with msvcrt:
import msvcrt, time
i = 0
while True:
i = i + 1
if msvcrt.kbhit():
if msvcrt.getwche() == '\r':
break
time.sleep(0.1)
print(i)
Sadly, still windows-specific.
On python 3.5 you can use the following code. It can be adjusted for a specific keystroke. The while loop will keep running until the user presses a key.
import time
import threading
# set global variable flag
flag = 1
def normal():
global flag
while flag==1:
print('normal stuff')
time.sleep(2)
if flag==False:
print('The while loop is now closing')
def get_input():
global flag
keystrk=input('Press a key \n')
# thread doesn't continue until key is pressed
print('You pressed: ', keystrk)
flag=False
print('flag is now:', flag)
n=threading.Thread(target=normal)
i=threading.Thread(target=get_input)
n.start()
i.start()
I could not get some of the popular answers working. So I came up with another approach using the CTRL + C to plug in user input and imbibe a keyboard interrupt. A simple solution can be using a try-catch block,
i = 0
try:
while True:
i+=1
print(i)
sleep(1)
except:
pass
# do what you want to do after it...
I got this idea from running a number of servers like flask and django. This might be slightly different from what the OP asked, but it might help someone else who wanted a similar thing.
Using the msvcrt module as thebjorn recommended I was able to come up with something that works. The following is a basic example that will exit the loop if any key is pressed, not just enter.
import msvcrt, time
i = 0
while True:
i = i + 1
if msvcrt.kbhit():
break
time.sleep(0.1)
print i
What you need is a non-blocking raw input, if you don't want to use threads there is a simple solution like this one below where he is doing a timeout of 20 ms and then raise and exception if the user doesn't press a key, if he does then the class returns the key pressed.
import signal
class AlarmException(Exception):
pass
def alarmHandler(signum, frame):
raise AlarmException
def nonBlockingRawInput(prompt='', timeout=20):
signal.signal(signal.SIGALRM, alarmHandler)
signal.alarm(timeout)
try:
text = raw_input(prompt)
signal.alarm(0)
return text
except AlarmException:
print '\nPrompt timeout. Continuing...'
signal.signal(signal.SIGALRM, signal.SIG_IGN)
return ''
Source code
I have defined the function which ask number input from the user and returns the factorial of that number. If user wants to stop they have to press 0 and then it will exit from the loop. We can specify any specific key to input 'n' to exit from the loop.
import math
def factorial_func(n):
return math.factorial(n)
while True:
n = int(input("Please enter the number to find factorial: "))
print(factorial_func(n))
if n == 0:
exit()
I am trying to run a program that takes in input as a job is getting done. I have looked through several forms, and looked into the documentation. I'm running this in Debian, and I understand that I can use this getch function to receive characters without hitting the return key. To break it down, this is what I am trying to implement in my infinite while loop
Take in input (threading didn't work here for me
Put input into Queue
If there are no running jobs, start the job with the item in front of the queue as a variable
I am also running the threading module to execute another instruction. Is there any way I can do this?
Update: This is what I have tried so far:
First, I tried using a timer from the threading module to stop it from waiting, which went something like this.
def getchnow():
def time_up():
answer= None
print 'time up...'
wait = Timer(5,time_up) # x is amount of time in seconds
wait.start()
try:
print "enter answer below"
answer = getch()
except Exception:
print 'pass\n'
answer = None
if answer != True: # it means if variable have somthing
wait.cancel() # time_up will not execute(so, no skip)
return answer
line = getchnow()
#Add line variable to queue
#Do stuff with queue
The problem here is that it still waited for user input.
I then tried to put the getch function into another thread.
q = Queue.Queue
q.put(getch())
if q.get() != True: # it means if variable have somthing
line = q.get()
#Add line variable to queue
#Do stuff with queue
This attempt doesn't let me do anything.
I read more of this link, and there was an implementation of what I wanted at the bottom.
I used the select module for a Non-Blocking implementation on Linux.
This times out in (5 seconds here) if no input is received.
Particularly useful when used in a thread, so that the getch call is
non-blocking and will allow the thread to exit cleanly
# This class gets a single character input from the keyboard
class _GetchUnix:
def __init__(self):
import tty, sys
from select import select
def __call__(self):
import sys, tty, termios
from select import select
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
[i, o, e] = select([sys.stdin.fileno()], [], [], 2)
if i:
ch=sys.stdin.read(1)
else:
ch=''
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
getch = _GetchUnix()
# End Class
I've also used [i, o, e] = select([sys.stdin.fileno()], [], [], 2), but I've heard it might not work on windows. If anyone still needs a multi-threaded, non-blocking input example:
import threading
import sys
import time
bufferLock=threading.Lock()
inputBuffer=[]
class InputThread(threading.Thread):
def run(self):
global inputBuffer
print("starting input")
while True:
line=sys.stdin.readline()
bufferLock.acquire()
inputBuffer.insert(0,line)
bufferLock.release()
input_thread=InputThread()
input_thread.start()
while True:
time.sleep(4)
bufferLock.acquire()
if len(inputBuffer)>0:
print("Popping: "+inputBuffer.pop())
bufferLock.release()
I have a test harness (written in Python) that needs to shut down the program under test (written in C) by sending it ^C. On Unix,
proc.send_signal(signal.SIGINT)
works perfectly. On Windows, that throws an error ("signal 2 is not supported" or something like that). I am using Python 2.7 for Windows, so I have the impression that I should be able to do instead
proc.send_signal(signal.CTRL_C_EVENT)
but this doesn't do anything at all. What do I have to do? This is the code that creates the subprocess:
# Windows needs an extra argument passed to subprocess.Popen,
# but the constant isn't defined on Unix.
try: kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
except AttributeError: pass
proc = subprocess.Popen(argv,
stdin=open(os.path.devnull, "r"),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
**kwargs)
There is a solution by using a wrapper (as described in the link Vinay provided) which is started in a new console window with the Windows start command.
Code of the wrapper:
#wrapper.py
import subprocess, time, signal, sys, os
def signal_handler(signal, frame):
time.sleep(1)
print 'Ctrl+C received in wrapper.py'
signal.signal(signal.SIGINT, signal_handler)
print "wrapper.py started"
subprocess.Popen("python demo.py")
time.sleep(3) #Replace with your IPC code here, which waits on a fire CTRL-C request
os.kill(signal.CTRL_C_EVENT, 0)
Code of the program catching CTRL-C:
#demo.py
import signal, sys, time
def signal_handler(signal, frame):
print 'Ctrl+C received in demo.py'
time.sleep(1)
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print 'demo.py started'
#signal.pause() # does not work under Windows
while(True):
time.sleep(1)
Launch the wrapper like e.g.:
PythonPrompt> import subprocess
PythonPrompt> subprocess.Popen("start python wrapper.py", shell=True)
You need to add some IPC code which allows you to control the wrapper firing the os.kill(signal.CTRL_C_EVENT, 0) command. I used sockets for this purpose in my application.
Explanation:
Preinformation
send_signal(CTRL_C_EVENT) does not work because CTRL_C_EVENT is only for os.kill. [REF1]
os.kill(CTRL_C_EVENT) sends the signal to all processes running in the current cmd window [REF2]
Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP) does not work because CTRL_C_EVENT is ignored for process groups. [REF2]
This is a bug in the python documentation [REF3]
Implemented solution
Let your program run in a different cmd window with the Windows shell command start.
Add a CTRL-C request wrapper between your control application and the application which should get the CTRL-C signal. The wrapper will run in the same cmd window as the application which should get the CTRL-C signal.
The wrapper will shutdown itself and the program which should get the CTRL-C signal by sending all processes in the cmd window the CTRL_C_EVENT.
The control program should be able to request the wrapper to send the CTRL-C signal. This might be implemnted trough IPC means, e.g. sockets.
Helpful posts were:
I had to remove the http in front of the links because I'm a new user and are not allowed to post more than two links.
http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/dc9586ab-1ee8-41aa-a775-cf4828ac1239/#6589714f-12a7-447e-b214-27372f31ca11
Can I send a ctrl-C (SIGINT) to an application on Windows?
Sending SIGINT to a subprocess of python
http://bugs.python.org/issue9524
http://ss64.com/nt/start.html
http://objectmix.com/python/387639-sending-cntrl-c.html#post1443948
Update: IPC based CTRL-C Wrapper
Here you can find a selfwritten python module providing a CTRL-C wrapping including a socket based IPC.
The syntax is quite similiar to the subprocess module.
Usage:
>>> import winctrlc
>>> p1 = winctrlc.Popen("python demo.py")
>>> p2 = winctrlc.Popen("python demo.py")
>>> p3 = winctrlc.Popen("python demo.py")
>>> p2.send_ctrl_c()
>>> p1.send_ctrl_c()
>>> p3.send_ctrl_c()
Code
import socket
import subprocess
import time
import random
import signal, os, sys
class Popen:
_port = random.randint(10000, 50000)
_connection = ''
def _start_ctrl_c_wrapper(self, cmd):
cmd_str = "start \"\" python winctrlc.py "+"\""+cmd+"\""+" "+str(self._port)
subprocess.Popen(cmd_str, shell=True)
def _create_connection(self):
self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._connection.connect(('localhost', self._port))
def send_ctrl_c(self):
self._connection.send(Wrapper.TERMINATION_REQ)
self._connection.close()
def __init__(self, cmd):
self._start_ctrl_c_wrapper(cmd)
self._create_connection()
class Wrapper:
TERMINATION_REQ = "Terminate with CTRL-C"
def _create_connection(self, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', port))
s.listen(1)
conn, addr = s.accept()
return conn
def _wait_on_ctrl_c_request(self, conn):
while True:
data = conn.recv(1024)
if data == self.TERMINATION_REQ:
ctrl_c_received = True
break
else:
ctrl_c_received = False
return ctrl_c_received
def _cleanup_and_fire_ctrl_c(self, conn):
conn.close()
os.kill(signal.CTRL_C_EVENT, 0)
def _signal_handler(self, signal, frame):
time.sleep(1)
sys.exit(0)
def __init__(self, cmd, port):
signal.signal(signal.SIGINT, self._signal_handler)
subprocess.Popen(cmd)
conn = self._create_connection(port)
ctrl_c_req_received = self._wait_on_ctrl_c_request(conn)
if ctrl_c_req_received:
self._cleanup_and_fire_ctrl_c(conn)
else:
sys.exit(0)
if __name__ == "__main__":
command_string = sys.argv[1]
port_no = int(sys.argv[2])
Wrapper(command_string, port_no)
New answer:
When you create the process, use the flag CREATE_NEW_PROCESS_GROUP. And then you can send CTRL_BREAK to the child process. The default behavior is the same as CTRL_C, except that it won't affect the calling process.
Old answer:
My solution also involves a wrapper script, but it does not need IPC, so it is far simpler to use.
The wrapper script first detaches itself from any existing console, then attach to the target console, then files the Ctrl-C event.
import ctypes
import sys
kernel = ctypes.windll.kernel32
pid = int(sys.argv[1])
kernel.FreeConsole()
kernel.AttachConsole(pid)
kernel.SetConsoleCtrlHandler(None, 1)
kernel.GenerateConsoleCtrlEvent(0, 0)
sys.exit(0)
The initial process must be launched in a separate console so that the Ctrl-C event will not leak. Example
p = subprocess.Popen(['some_command'], creationflags=subprocess.CREATE_NEW_CONSOLE)
# Do something else
subprocess.check_call([sys.executable, 'ctrl_c.py', str(p.pid)]) # Send Ctrl-C
where I named the wrapper script as ctrl_c.py.
Try calling the GenerateConsoleCtrlEvent function using ctypes. As you are creating a new process group, the process group ID should be the same as the pid. So, something like
import ctypes
ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, proc.pid) # 0 => Ctrl-C
should work.
Update: You're right, I missed that part of the detail. Here's a post which suggests a possible solution, though it's a bit kludgy. More details are in this answer.
Here is a fully working example which doesn't need any modification in the target script.
This overrides the sitecustomize module so it might no be suitable for every scenario. However, in this case you could use a *.pth file in site-packages to execute code at the subprocess startup (see https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html).
Edit This works only out of the box for subprocesses in Python. Other processes have to manually call SetConsoleCtrlHandler(NULL, FALSE).
main.py
import os
import signal
import subprocess
import sys
import time
def main():
env = os.environ.copy()
env['PYTHONPATH'] = '%s%s%s' % ('custom-site', os.pathsep,
env.get('PYTHONPATH', ''))
proc = subprocess.Popen(
[sys.executable, 'sub.py'],
env=env,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
)
time.sleep(1)
proc.send_signal(signal.CTRL_C_EVENT)
proc.wait()
if __name__ == '__main__':
main()
custom-site\sitecustomize.py
import ctypes
import sys
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
if not kernel32.SetConsoleCtrlHandler(None, False):
print('SetConsoleCtrlHandler Error: ', ctypes.get_last_error(),
file=sys.stderr)
sub.py
import atexit
import time
def cleanup():
print ('cleanup')
atexit.register(cleanup)
while True:
time.sleep(1)
I have a single file solution with the following advantages:
- No external libraries. (Other than ctypes)
- Doesn't require the process to be opened in a specific way.
The solution is adapted from this stack overflow post, but I think it's much more elegant in python.
import os
import signal
import subprocess
import sys
import time
# Terminates a Windows console app sending Ctrl-C
def terminateConsole(processId: int, timeout: int = None) -> bool:
currentFilePath = os.path.abspath(__file__)
# Call the below code in a separate process. This is necessary due to the FreeConsole call.
try:
code = subprocess.call('{} {} {}'.format(sys.executable, currentFilePath, processId), timeout=timeout)
if code == 0: return True
except subprocess.TimeoutExpired:
pass
# Backup plan
subprocess.call('taskkill /F /PID {}'.format(processId))
if __name__ == '__main__':
pid = int(sys.argv[1])
import ctypes
kernel = ctypes.windll.kernel32
r = kernel.FreeConsole()
if r == 0: exit(-1)
r = kernel.AttachConsole(pid)
if r == 0: exit(-1)
r = kernel.SetConsoleCtrlHandler(None, True)
if r == 0: exit(-1)
r = kernel.GenerateConsoleCtrlEvent(0, 0)
if r == 0: exit(-1)
r = kernel.FreeConsole()
if r == 0: exit(-1)
# use tasklist to wait while the process is still alive.
while True:
time.sleep(1)
# We pass in stdin as PIPE because there currently is no Console, and stdin is currently invalid.
searchOutput: bytes = subprocess.check_output('tasklist /FI "PID eq {}"'.format(pid), stdin=subprocess.PIPE)
if str(pid) not in searchOutput.decode(): break;
# The following two commands are not needed since we're about to close this script.
# You can leave them here if you want to do more console operations.
r = kernel.SetConsoleCtrlHandler(None, False)
if r == 0: exit(-1)
r = kernel.AllocConsole()
if r == 0: exit(-1)
exit(0)
For those interested in a "quick fix", I've made a console-ctrl package based on Siyuan Ren's answer to make it even easier to use.
Simply run pip install console-ctrl, and in your code:
import console_ctrl
import subprocess
# Start some command IN A SEPARATE CONSOLE
p = subprocess.Popen(['some_command'], creationflags=subprocess.CREATE_NEW_CONSOLE)
# ...
# Stop the target process
console_ctrl.send_ctrl_c(p.pid)
I have been trying this but for some reason ctrl+break works, and ctrl+c does not. So using os.kill(signal.CTRL_C_EVENT, 0) fails, but doing os.kill(signal.CTRL_C_EVENT, 1) works. I am told this has something to do with the create process owner being the only one that can pass a ctrl c? Does that make sense?
To clarify, while running fio manually in a command window it appears to be running as expected. Using the CTRL + BREAK breaks without storing the log as expected and CTRL + C finishes writing to the file also as expected. The problem appears to be in the signal for the CTRL_C_EVENT.
It almost appears to be a bug in Python but may rather be a bug in Windows. Also one other thing, I had a cygwin version running and sending the ctrl+c in python there worked as well, but then again we aren't really running native windows there.
example:
import subprocess, time, signal, sys, os
command = '"C:\\Program Files\\fio\\fio.exe" --rw=randrw --bs=1M --numjobs=8 --iodepth=64 --direct=1 ' \
'--sync=0 --ioengine=windowsaio --name=test --loops=10000 ' \
'--size=99901800 --rwmixwrite=100 --do_verify=0 --filename=I\\:\\test ' \
'--thread --output=C:\\output.txt'
def signal_handler(signal, frame):
time.sleep(1)
print 'Ctrl+C received in wrapper.py'
signal.signal(signal.SIGINT, signal_handler)
print 'command Starting'
subprocess.Popen(command)
print 'command started'
time.sleep(15)
print 'Timeout Completed'
os.kill(signal.CTRL_C_EVENT, 0)
(This was supposed to be a comment under Siyuan Ren's answer but I don't have enough rep so here's a slightly longer version.)
If you don't want to create any helper scripts you can use:
p = subprocess.Popen(['some_command'], creationflags=subprocess.CREATE_NEW_CONSOLE)
# Do something else
subprocess.run([
sys.executable,
"-c",
"import ctypes, sys;"
"kernel = ctypes.windll.kernel32;"
"pid = int(sys.argv[1]);"
"kernel.FreeConsole();"
"kernel.AttachConsole(pid);"
"kernel.SetConsoleCtrlHandler(None, 1);"
"kernel.GenerateConsoleCtrlEvent(0, 0);"
"sys.exit(0)",
str(p.pid)
]) # Send Ctrl-C
But it won't work if you use PyInstaller - sys.executable points to your executable, not the Python interpreter. To solve that issue I've created a tiny utility for Windows: https://github.com/anadius/ctrlc
Now you can send the Ctrl+C event with:
subprocess.run(["ctrlc", str(p.pid)])
Let's say I have a python program that is spitting out lines of text, such as:
while 1:
print "This is a line"
What's the easiest way to allow one to press a key on the keyboard to pause the loop, then to resume if pressed again---but if nothing is pressed it should just continue on automatically?
I'm hoping I don't have to go into something like curses to get this!
You could try this implementation for Linux / Mac (and possible other Unices) (code attribution: found on ActiveState Code Recipes).
On Windows you should check out msvcrt.
import sys, termios, atexit
from select import select
# save the terminal settings
fd = sys.stdin.fileno()
new_term = termios.tcgetattr(fd)
old_term = termios.tcgetattr(fd)
# new terminal setting unbuffered
new_term[3] = (new_term[3] & ~termios.ICANON & ~termios.ECHO)
# switch to normal terminal
def set_normal_term():
termios.tcsetattr(fd, termios.TCSAFLUSH, old_term)
# switch to unbuffered terminal
def set_curses_term():
termios.tcsetattr(fd, termios.TCSAFLUSH, new_term)
def putch(ch):
sys.stdout.write(ch)
def getch():
return sys.stdin.read(1)
def getche():
ch = getch()
putch(ch)
return ch
def kbhit():
dr,dw,de = select([sys.stdin], [], [], 0)
return dr <> []
Implementing what you're looking for would then become something like this:
atexit.register(set_normal_term)
set_curses_term()
while True:
print "myline"
if kbhit():
print "paused..."
ch = getch()
while True
if kbhit():
print "unpaused..."
ch = getch()
break
The easiest way for me, assuming I was working in bash, would be to hit Control-Z to suspend the job, then use the 'fg' command to restore it when I was ready. But since I don't know what platform you're using, I'll have to go with using ChristopheD's solution as your best starting point.
When you press Ctrl+C, a KeyboardInterrupt exception gets raised in your program. You can catch that exception to create the behavior you want - for instance, to pause the program and resume after 5s:
import time
while True:
try:
# This is where you're doing whatever you're doing
print("This is a line")
except KeyboardInterrupt:
print("Paused! Ctrl+C again within 5 seconds to kill program")
# A second KeyboardInterrupt that happens during this sleep will
# not be caught, so it will terminate the program
time.sleep(5)
print("Continuing...")
Or to pause the program indefinitely until the user hits 'enter':
while True:
try:
# This is where you're doing whatever you're doing
print("This is a line")
except KeyboardInterrupt:
print("Interrupted!")
input("Press enter to continue, or Ctrl+C to terminate.")
print("Continuing...")
If you want to catch a second KeyboardInterrupt as well and do something fancy with that, you can do so by nesting try/except blocks, though I wouldn't really recommend that - it's a good idea to allow a string of KeyboardInterrupts to terminate the program.