Come uccidere un ciclo while con una sequenza di tasti?


88

Sto leggendo dati seriali e scrivendo su un file csv usando un ciclo while. Voglio che l'utente sia in grado di terminare il ciclo while una volta che sente di aver raccolto dati sufficienti.

while True:
    #do a bunch of serial stuff

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

Ho fatto qualcosa di simile usando opencv, ma non sembra funzionare in questa applicazione (e comunque non voglio importare opencv solo per questa funzione) ...

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

Così. Come posso lasciare che l'utente esca dal giro?

Inoltre, non voglio usare l'interruzione della tastiera, perché lo script deve continuare a essere eseguito dopo che il ciclo while è terminato.

Risposte:


144

Il modo più semplice è interromperlo semplicemente con il solito Ctrl-C(SIGINT).

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

Dal momento che le Ctrl-Ccause vengono KeyboardInterruptsollevate, basta prenderlo fuori dal ciclo e ignorarlo.


2
@ Chris: perché non ci provi. (e poi commenta)
SilentGhost

Questo arresto anomalo (ricevo la traccia degli errori) ^Cviene emesso mentre si è in do_something(). Come puoi evitarlo?
Atcold

Il mio do_something()legge alcuni valori dall'USB, quindi se ^Cviene emesso mentre sono dentro do_something()ricevo fastidiosi errori di comunicazione. Invece, se sono dentro while, fuori do_something(), tutto è liscio. Quindi, mi chiedevo come gestire questa situazione. Non sono sicuro di essere stato abbastanza chiaro.
Atcold

@Atcold Quindi hai un modulo di estensione compilato che stai utilizzando. Che tipo di modulo è? È una libreria C comune che viene incapsulata?
Keith

Ho una chiamata pyVISAe una chiamata a matplotlib, in modo da poter avere la visualizzazione in tempo reale delle mie misurazioni. E a volte ricevo errori funky. Penso che dovrei aprire una domanda separata e smetterla di inquinare la tua risposta ...
Atcold

36

Esiste una soluzione che non richiede moduli fuori standard ed è trasportabile al 100%

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

5
Solo una nota per coloro che usano Python 3+: raw_input () è stato rinominato input (), e il modulo thread è ora _thread.
Wieschie

Non funzionava in python 3, secondo la documentazione di python 3: "I thread interagiscono in modo strano con gli interrupt: l'eccezione KeyboardInterrupt verrà ricevuta da un thread arbitrario. (Quando il modulo signal è disponibile, gli interrupt vanno sempre al thread principale)."
Towhid

@Towhid Ma questo non usa interruzioni. Utilizza la lettura da stdin.
Artyer

@Artyer Se non vado errato, tutte le sequenze di tasti generano interrupt, poiché vengono attivati ​​da un hardware. questo codice ha funzionato per te e, in tal caso, hai apportato modifiche specifiche?
Towhid

2
@Towhid solo thread-> _threade raw_input-> input. Devi premere Invio per alimentare la linea. Se vuoi fare su qualsiasi tasto, usa getch .
Artyer

14

il codice seguente funziona per me. Richiede openCV (import cv2).

Il codice è composto da un ciclo infinito che è alla continua ricerca di un tasto premuto. In questo caso, premendo il tasto 'q', il programma termina. È possibile premere altri tasti (in questo esempio "b" o "k") per eseguire diverse azioni come modificare il valore di una variabile o eseguire una funzione.

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 ...

5
Bene, ma cv2 è troppo pesante, a meno che tu non lo stia già usando per qualcos'altro.
ogurets

1
perché AND con 255
Talespin_Kit

@Talespin_Kit & 0xff "maschera la variabile in modo che lasci solo il valore negli ultimi 8 bit e ignori tutto il resto dei bit. Fondamentalmente garantisce che il risultato sia compreso tra 0 e 255. Nota che non lo faccio mai in opencv e le cose funzionano bene.
eric

7

Per Python 3.7, ho copiato e modificato la bellissima risposta di user297171 in modo che funzioni in tutti gli scenari in Python 3.7 che ho testato.

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

Non so se sto facendo qualcosa di sbagliato o cosa, ma non riesco a capire come fermare questo loop? Come si fa a farlo?
Mihkel

@ Mihkel devi premere il tasto <Invio>. Ciò causerà la chiusura del ciclo.
rayzinnz

Questo è accettabile, ma non generalizza a chiavi diverse da invio.
John Forbes

non funziona per me su python2.7 ma funziona su python3
crazjo

fare multithreading è quello che ho in mente, ma mi piace abbastanza la risposta di @Keith sopra. Abbastanza semplice e chiaro.
dipendente


1

C'è sempre sys.exit().

La libreria di sistema nella libreria principale di Python ha una funzione di uscita che è molto utile durante la prototipazione. Il codice sarebbe sulla falsariga di:

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

in python 3 raw_inputè sostituito dainput
Talha Anwar

1

Ho modificato la risposta di rayzinnz per terminare lo script con una chiave specifica, in questo caso la chiave di uscita

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

Ciao! Sebbene questo codice possa risolvere la domanda, inclusa una spiegazione di come e perché questo risolve il problema aiuterebbe davvero a migliorare la qualità del tuo post e probabilmente si tradurrebbe in più voti positivi. Ricorda che stai rispondendo alla domanda per i lettori in futuro, non solo alla persona che lo chiede ora. Si prega di modificare la risposta per aggiungere spiegazioni e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni.
Brian

1

Seguendo questo thread nella tana del coniglio, sono arrivato a questo, funziona su Win10 e Ubuntu 20.04. Volevo qualcosa di più che uccidere lo script e utilizzare chiavi specifiche, e doveva funzionare sia su MS che su 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()

0

Può essere utile installare 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 

0

Questa è la soluzione che ho trovato con i thread e le librerie standard Il

ciclo continua finché non viene premuto un tasto
Restituisce il tasto premuto come una singola stringa di caratteri

Funziona in Python 2.7 e 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()

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

per entrare usa 'ENTER'

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.