¿Cómo matar un bucle while con una pulsación de tecla?

86

Estoy leyendo datos en serie y escribiendo en un archivo csv usando un bucle while. Quiero que el usuario pueda eliminar el ciclo while una vez que sienta que ha recopilado suficientes datos.

while True:
    #do a bunch of serial stuff

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

He hecho algo como esto usando opencv, pero parece que no funciona en esta aplicación (y de todos modos no quiero importar opencv solo para esta función) ...

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

Entonces. ¿Cómo puedo dejar que el usuario se salga del círculo?

Además, no quiero usar la interrupción del teclado, porque el script debe continuar ejecutándose después de que finalice el ciclo while.

Chris
fuente

Respuestas:

143

La forma más fácil es simplemente interrumpirlo con el Ctrl-C(SIGINT) habitual .

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

Dado que las Ctrl-Ccausas KeyboardInterruptse generan, simplemente retírelo fuera del bucle e ignórelo.

Keith
fuente
2
@Chris: ¿por qué no lo intentas? (y luego comentar)
SilentGhost
Esto se bloquea (obtengo un seguimiento de error) ^Cse emite mientras está en do_something(). ¿Cómo se puede evitar esto?
Atcold
Mi do_something()lee algunos valores del USB, así que, si ^Cse emite mientras estoy adentro do_something(), obtengo desagradables errores de comunicación. En cambio, si estoy en el while, fuera del do_something(), todo está bien. Entonces, me preguntaba cómo manejar esta situación. No estoy seguro de haberme aclarado lo suficiente.
Atcold
@Atcold Entonces tienes un módulo de extensión compilado que estás usando. ¿Qué tipo de módulo es? ¿Es una biblioteca C común que se está empaquetando?
Keith
Tengo una llamada pyVISAy una llamada para matplotlibpoder tener una visualización en vivo de mis medidas. Y a veces tengo errores raros. Creo que debería abrir una pregunta separada y dejar de contaminar tu respuesta ...
Atcold
34

Existe una solución que no requiere módulos no estándar y es 100% transportable

import thread

def input_thread(a_list):
    raw_input()
    a_list.append(True)

def do_stuff():
    a_list = []
    thread.start_new_thread(input_thread, (a_list,))
    while not a_list:
        stuff()

fuente
4
Solo una nota para aquellos que usan Python 3+: raw_input () ha sido renombrado a input (), y el módulo de hilo ahora es _thread.
Wieschie
No funcionó en Python 3, según los documentos de Python 3: "Los subprocesos interactúan de manera extraña con las interrupciones: la excepción KeyboardInterrupt será recibida por un subproceso arbitrario. (Cuando el módulo de señal está disponible, las interrupciones siempre van al subproceso principal)".
Towhid
@Towhid Pero esto no usa interrupciones. Utiliza la lectura de stdin.
Artyer
@Artyer Si no me equivoco, todas las pulsaciones provocan interrupciones, ya que son provocadas por un hardware. ¿Este código funcionó para usted y, de ser así, realizó algún cambio específico?
Towhid
2
@Towhid solo thread-> _thready raw_input-> input. Tienes que presionar enter para alimentar la línea. Si quieres hacerlo con cualquier tecla, usa getch .
Artyer
14

el siguiente código funciona para mí. Requiere openCV (importar cv2).

El código está compuesto por un bucle infinito que busca continuamente una tecla presionada. En este caso, cuando se presiona la tecla 'q', el programa finaliza. Se pueden presionar otras teclas (en este ejemplo, 'b' o 'k') para realizar diferentes acciones, como cambiar el valor de una variable o ejecutar una función.

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 ...
Luis Jose
fuente
5
Bien, pero cv2 es demasiado pesado, a menos que ya lo esté usando para otra cosa.
ogurets
1
por qué Y con 255
Talespin_Kit
@Talespin_Kit & 0xff ”enmascara la variable de modo que deja solo el valor en los últimos 8 bits e ignora el resto de los bits. Básicamente, asegura que el resultado estará dentro de 0-255. Tenga en cuenta que nunca hago esto en opencv y las cosas funcionan bien.
Eric
6

Para Python 3.7, copié y cambié la muy buena respuesta por user297171 para que funcione en todos los escenarios en Python 3.7 que probé.

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
fuente
No sé si estoy haciendo algo mal o qué, pero no sé cómo detener este bucle. ¿Cómo haces eso?
Mihkel
@Mihkel tienes que presionar la tecla <Enter>. Esto hará que el bucle salga.
rayzinnz
Esto es decente, pero no se generaliza a otras teclas que no sean enter.
John Forbes
no funciona para mí en python2.7 pero funciona en python3
crazjo
hacer multihilo es lo que también tengo en mente, pero me gusta bastante la respuesta de @Keith anterior. Lo suficientemente simple y claro.
adicto
1

Siempre la hay sys.exit().

La biblioteca del sistema en la biblioteca central de Python tiene una función de salida que es muy útil cuando se crean prototipos. El código estaría en la línea de:

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
fuente
en Python 3 raw_inputse reemplaza porinput
Talha Anwar
1

Modifiqué la respuesta de rayzinnz para finalizar el script con una clave específica, en este caso la clave de escape

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()
Pascal Wendler
fuente
¡Hola! Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación y probablemente resultaría en más votos a favor. Recuerde que está respondiendo la pregunta a los lectores en el futuro, no solo a la persona que pregunta ahora. Por favor, editar su respuesta para agregar explicaciones y dar una indicación de lo que se aplican limitaciones y supuestos.
Brian
1

Después de seguir este hilo por la madriguera del conejo, llegué a esto, funciona en Win10 y Ubuntu 20.04. Quería algo más que matar el script y usar claves específicas, y tenía que funcionar tanto en MS como en 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()
ArthurH
fuente
0

Esto puede ser útil para instalar pynput con - 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 
ANKIT YADAV
fuente
0

Esta es la solución que encontré con subprocesos y bibliotecas estándar El

bucle continúa hasta que se presiona una tecla
Devuelve la tecla presionada como una cadena de un solo carácter

Funciona en Python 2.7 y 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
fuente
-1
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('  :( ')

para ingresar use 'ENTER'

Taimoor Arif
fuente