122

I am reading serial data and writing to a csv file using a while loop. I want the user to be able to kill the while loop once they feel they have collected enough data.

while True:
    #do a bunch of serial stuff

    #if the user presses the 'esc' or 'return' key:
        break

I have done something like this using opencv, but it doesn't seem to be working in this application (and i really don't want to import opencv just for this function anyway)...

        # Listen for ESC or ENTER key
        c = cv.WaitKey(7) % 0x100
        if c == 27 or c == 10:
            break

So. How can I let the user break out of the loop?

Also, I don't want to use keyboard interrupt, because the script needs to continue to run after the while loop is terminated.

Chris
  • 9,603
  • 15
  • 46
  • 67

19 Answers19

187

The easiest way is to just interrupt it with the usual Ctrl-C (SIGINT).

try:
    while True:
        do_something()
except KeyboardInterrupt:
    pass

Since Ctrl-C causes KeyboardInterrupt to be raised, just catch it outside the loop and ignore it.

SilentGhost
  • 307,395
  • 66
  • 306
  • 293
Keith
  • 42,110
  • 11
  • 57
  • 76
  • 2
    @Chris: why don't you give it a try. (and then comment) – SilentGhost Nov 01 '12 at 16:21
  • 1
    This crashes (I get error trace back) is `^C` is issued while in `do_something()`. How can you avoid this? – Atcold Apr 20 '16 at 19:00
  • 2
    My `do_something()` reads some values from the USB, so, if `^C` is issued while I'm inside `do_something()` I get nasty communication errors. Instead, if I'm in the `while`, outside the `do_something()`, all is smooth. So, I was wondering how to handle this situation. I'm not sure I made myself clear enough. – Atcold Apr 23 '16 at 04:43
  • 1
    @Atcold So you have a compiled extension module that you're using. What kind of module is it? Is it a common C library being wrapped? – Keith Apr 23 '16 at 04:52
  • 1
    I have a call to `pyVISA` and a call to `matplotlib`, so that I can have live visualisation of my measurements. And I get funky errors sometimes. I think I should open a separate question and stop polluting your answer... – Atcold Apr 25 '16 at 13:54
  • @Atcold Exceptions and other interruptions will always interrupt reads- there's nothing you can do about that. – user1280483 Dec 04 '21 at 16:27
  • I see, thank you. I didn't know that. – Atcold Dec 04 '21 at 17:23
48

There is a solution that requires no non-standard modules and is 100% transportable:

import _thread

def input_thread(a_list):
    raw_input()             # use input() in Python3
    a_list.append(True)
    
def do_stuff():
    a_list = []
    _thread.start_new_thread(input_thread, (a_list,))
    while not a_list:
        stuff()
MoshiBin
  • 3,107
  • 2
  • 21
  • 23
  • 7
    Just a note for those using Python 3+: raw_input() has been renamed to input(), and the thread module is now _thread. – Wieschie Sep 21 '16 at 19:23
  • Didn't work in python 3, according to python 3 docs: "Threads interact strangely with interrupts: the KeyboardInterrupt exception will be received by an arbitrary thread. (When the signal module is available, interrupts always go to the main thread.)" – Towhid Mar 30 '17 at 15:41
  • @Towhid But this doesn't use interrupts. It uses reading from stdin. – Artyer Jun 07 '17 at 19:28
  • 1
    @Artyer If I'm not mistaken all keystrokes raise interrupts, since they are raised by a hardware. did this code work for you, and if so did you make any specific changes? – Towhid Jun 11 '17 at 20:42
  • 2
    @Towhid just `thread` -> `_thread` and `raw_input` -> `input`. You have to press enter to feed the line. If you want to do on any key, use [getch](https://stackoverflow.com/questions/510357/python-read-a-single-character-from-the-user). – Artyer Jun 13 '17 at 10:17
  • I had the same scenario as OP, and this solution worked perfectly in Python 3 without having to wrap everything in a try/except loop waiting for KeyboardInterrupt like the "accepted answer" suggests. – Steven Teglman Mar 11 '22 at 08:59
  • Beauty of a solution – Diesel Feb 17 '23 at 18:38
17

the following code works for me. It requires openCV (import cv2).

The code is composed of an infinite loop that is continuously looking for a key pressed. In this case, when the 'q' key is pressed, the program ends. Other keys can be pressed (in this example 'b' or 'k') to perform different actions such as change a variable value or execute a function.

import cv2

while True:
    k = cv2.waitKey(1) & 0xFF
    # press 'q' to exit
    if k == ord('q'):
        break
    elif k == ord('b'):
        # change a variable / do something ...
    elif k == ord('k'):
        # change a variable / do something ...
Danila Ganchar
  • 10,266
  • 13
  • 49
  • 75
Luis Jose
  • 664
  • 6
  • 8
  • 8
    Good, but cv2 is too heavy, unless you're already using it for something else. – ogurets Jun 02 '17 at 15:11
  • 1
    why AND with 255 – Talespin_Kit Mar 02 '19 at 19:42
  • @Talespin_Kit & 0xff” masks the variable so it leaves only the value in the last 8 bits, and ignores all the rest of the bits. Basically it ensures the result will be within 0-255. Note I don't ever do this in opencv and things work fine. – eric Jan 09 '20 at 16:59
14

For Python 3.7, I copied and changed the very nice answer by user297171 so it works in all scenarios in Python 3.7 that I tested.

import threading as th

keep_going = True
def key_capture_thread():
    global keep_going
    input()
    keep_going = False

def do_stuff():
    th.Thread(target=key_capture_thread, args=(), name='key_capture_thread', daemon=True).start()
    while keep_going:
        print('still going...')

do_stuff()
rayzinnz
  • 1,639
  • 1
  • 17
  • 17
12
pip install keyboard

import keyboard

while True:
    # do something
    if keyboard.is_pressed("q"):
        print("q pressed, ending loop")
        break
user15083300
  • 129
  • 1
  • 2
  • 3
    Hi New user, thanks for your answer. Could you add some more explanation as to how this works and if necessary any supporting documentation links. Just pasting code, isn't always helpful and describing the solution will help future readers understand if your answer is suitable for them. – PeterS Jan 26 '21 at 11:33
  • I think this is the correct and certainly simplest solution to the original question.Worked on windows 10, python 3.8 – rhody May 07 '21 at 00:19
  • 3
    This one doesn't work in *nix systems except when the user is root (aka never) – László Báthory Sep 08 '21 at 13:33
5

Here is a solution that worked for me. Got some ideas from posts here and elsewhere. Loop won't end until defined key (abortKey) is pressed. The loop stops as fast as possible and does not try to run to next iteration.

from pynput import keyboard
from threading import Thread
from time import sleep

def on_press(key, abortKey='esc'):    
    try:
        k = key.char  # single-char keys
    except:
        k = key.name  # other keys    

    print('pressed %s' % (k))
    if k == abortKey:
        print('end loop ...')
        return False  # stop listener

def loop_fun():
    while True:
        print('sleeping')
        sleep(5)
        
if __name__ == '__main__':
    abortKey = 't'
    listener = keyboard.Listener(on_press=on_press, abortKey=abortKey)
    listener.start()  # start to listen on a separate thread

    # start thread with loop
    Thread(target=loop_fun, args=(), name='loop_fun', daemon=True).start()

    listener.join() # wait for abortKey
LuettgeM
  • 133
  • 2
  • 4
4

pyHook might help. http://sourceforge.net/apps/mediawiki/pyhook/index.php?title=PyHook_Tutorial#tocpyHook%5FTutorial4

See keyboard hooks; this is more generalized-- if you want specific keyboard interactions and not just using KeyboardInterrupt.

Also, in general (depending on your use) I think having the Ctrl-C option still available to kill your script makes sense.

See also previous question: Detect in python which keys are pressed

Community
  • 1
  • 1
Anov
  • 2,272
  • 2
  • 20
  • 26
2

There is always sys.exit().

The system library in Python's core library has an exit function which is super handy when prototyping. The code would be along the lines of:

import sys

while True:
    selection = raw_input("U: Create User\nQ: Quit")
    if selection is "Q" or selection is "q":
        print("Quitting")
        sys.exit()
    if selection is "U" or selection is "u":
        print("User")
        #do_something()
Julian Wise
  • 380
  • 1
  • 3
  • 16
2

From following this thread down the rabbit hole, I came to this, works on Win10 and Ubuntu 20.04. I wanted more than just killing the script, and to use specific keys, and it had to work in both MS and Linux..

import _thread
import time
import sys
import os

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
        msvcrt_char = msvcrt.getch()
        return msvcrt_char.decode("utf-8")

def input_thread(key_press_list):
    char = 'x'
    while char != 'q': #dont keep doing this after trying to quit, or 'stty sane' wont work
        time.sleep(0.05)
        getch = _Getch()
        char = getch.impl()
        pprint("getch: "+ str(char))
        key_press_list.append(char)

def quitScript():
    pprint("QUITTING...")
    time.sleep(0.2) #wait for the thread to die
    os.system('stty sane')
    sys.exit()

def pprint(string_to_print): #terminal is in raw mode so we need to append \r\n
    print(string_to_print, end="\r\n")

def main():
    key_press_list = []
    _thread.start_new_thread(input_thread, (key_press_list,))
    while True:
        #do your things here
        pprint("tick")
        time.sleep(0.5)

        if key_press_list == ['q']:
            key_press_list.clear()
            quitScript()

        elif key_press_list == ['j']:
            key_press_list.clear()
            pprint("knock knock..")

        elif key_press_list:
            key_press_list.clear()

main()
Steven
  • 1,733
  • 2
  • 16
  • 30
ArthurH
  • 61
  • 3
1

I modified the answer from rayzinnz to end the script with a specific key, in this case the escape key

import threading as th
import time
import keyboard

keep_going = True
def key_capture_thread():
    global keep_going
    a = keyboard.read_key()
    if a== "esc":
        keep_going = False


def do_stuff():
    th.Thread(target=key_capture_thread, args=(), name='key_capture_thread', daemon=True).start()
    i=0
    while keep_going:
        print('still going...')
        time.sleep(1)
        i=i+1
        print (i)
    print ("Schleife beendet")


do_stuff()
  • 1
    Hello! While this code may solve the question, [including an explanation](https://meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – Brian61354270 Apr 24 '20 at 13:27
1

This is the solution I found with threads and standard libraries

Loop keeps going on until one key is pressed
Returns the key pressed as a single character string

Works in Python 2.7 and 3

import thread
import sys

def getch():
    import termios
    import sys, tty
    def _getch():
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch
    return _getch()

def input_thread(char):
    char.append(getch())

def do_stuff():
    char = []
    thread.start_new_thread(input_thread, (char,))
    i = 0
    while not char :
        i += 1

    print "i = " + str(i) + " char : " + str(char[0])

do_stuff()
Berni Gf
  • 161
  • 1
  • 5
  • 1
    This is fantastic! Only thing I had to change is to use 'import _thread' instead of 'import thread'. Working great on Debian Bullseye and Raspbian 11. Thank you! – captcha May 15 '22 at 08:16
1

The accepted answer with KeyboardInterrupt was unreliable for me, but the following solution with pyinput worked (Python 3.10, Linux). The while-loop ends when q is pressed:

from pynput.keyboard import Listener  # pip install pynput

keyboard_quit = False

def keyboard_handler(key):
    global keyboard_quit
    if hasattr(key, 'char') and key.char == 'q':
        keyboard_quit = True

keyboard_listener = Listener(on_press=keyboard_handler)
keyboard_listener.start()  # Non-blocking

while not keyboard_quit:
    # Do something
aimfeld
  • 2,931
  • 7
  • 32
  • 43
0

This may be helpful install pynput with -- pip install pynput

from pynput.keyboard import Key, Listener
def on_release(key):
    if key == Key.esc:
        # Stop listener
        return False

# Collect events until released
while True:
    with Listener(
            on_release=on_release) as listener:
        listener.join()
    break 
0

Here is a simple Windows solution that safely ends current iteration and then quits. I used it with a counter example that breaks the loop with 'Esc' key and quits. It uses kbhit() and getch() functions from msvcrt package. Time package is only called for easement reasons (to set time delay between events).

import msvcrt, time

print("Press 'Esc' to stop the loop...")
x = 0
while True:
    x += 1
    time.sleep(0.5)
    print(x)
    
    if msvcrt.kbhit():
        if msvcrt.getch() == b'\x1b':
            print("You have pressed Esc! See you!")
            time.sleep(2)    
            break

kbhit() function returns True if a keypress is waiting to be read

getch() function reads a keypress and returns the resulting character as a byte string. It can be used with any key

b'\x1b' is the byte string character for the 'Esc' key.

Facu
  • 101
  • 6
0

Here another example using threading.Event, without the need for catching SIGINT (Ctrl+c).

As @Atcold has mentioned in a comment below the accepted answer, pressing Ctrl+c in the loop, may interrupt a long running operation and leave it in an undefined state. This can specially annoying, when that long running operation comes from a library that you are calling.

In the example below, the user needs to press q and then press Enter. If you want to capture the key stroke immediately, you need something like _Getch() from this answer.

import time
from threading import Thread, Event


def read_input(q_entered_event):
    c = input()
    if c == "q":
        print("User entered q")
        q_entered_event.set()


def do_long_running_stuff():
    q_pressed_event = Event()
    input_thread = Thread(target=read_input,
                          daemon=True,
                          args=(q_pressed_event,))
    input_thread.start()
    while True:
        print("I am working ...")
        time.sleep(1)
        if q_pressed_event.is_set():
            break
    
    print("Process stopped by user.")


if __name__  == "__main__":
    do_long_running_stuff()
zardosht
  • 3,014
  • 2
  • 24
  • 32
0
from time import sleep
from threading import Thread
import threading

stop_flag = 0
    
    def Wait_Char():
        global stop_flag
        v = input("Enter Char")
        if(v == "z"):
            stop_flag = 1
    
    
    def h():
        while(True):
            print("Hello Feto")
            time.sleep(1)
            if(stop_flag == 1):
                break
    
    
    thread1 = Thread(target=Wait_Char)
    thread2 = Thread(target=h)
    thread1.start()
    thread2.start()
    print("threads finished...exiting")

This isn't the best way but it can do the job you want,
Running 2 Threads one waiting for the Key you want to stop the loop with
(Wait_Char Method)
and one for loop
(H Method)
And both see a global variable stop_flag which control the stoping process Stop when I press z

Mohamed Fathallah
  • 1,274
  • 1
  • 15
  • 17
0
from pynput import keyboard

def on_press(key):
    if key == keyboard.Key.esc:
        return False

i = 0
with keyboard.Listener(on_press=on_press) as listener:
    # Your infinite loop
    while listener.running:
        print(i)
        i=i+1
print("Done")

It works ...

Shay
  • 15
  • 5
0

you can use keyboard in python 3.11.

pip install keyboard

answer:

from keyboard import add_hotkey, remove_hotkey
from time import sleep

def break_loop():
    global stop
    stop = True

add_hotkey("q", break_loop)
stop = False
while True:
    print("Do something...")
    sleep(1)
    if stop == True:
        break
remove_hotkey("q")

if you work with sleep and you have 10s sleep, you can do this:

from keyboard import add_hotkey, remove_hotkey
from time import sleep

def break_loop():
    global stop
    stop = True

add_hotkey("q", break_loop)
stop = False
while True:
    print("Do something...")
    for i in range(10): # Waiting
        sleep(1) # Split 10 seconds for fast break
        if stop == True: # First break
            break
    if stop == True: # Second break
        break
remove_hotkey("q")
-5
import keyboard

while True:
    print('please say yes')
    if keyboard.is_pressed('y'):
         break
print('i got u :) ')
print('i was trying to write you are a idiot ')
print('  :( ')

for enter use 'ENTER'