Como matar um loop while com uma tecla?

86

Estou lendo dados seriais e gravando em um arquivo csv usando um loop while. Desejo que o usuário seja capaz de eliminar o loop while assim que sentir que coletou dados suficientes.

while True:
    #do a bunch of serial stuff

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

Eu fiz algo assim usando o opencv, mas não parece estar funcionando neste aplicativo (e eu realmente não quero importar o opencv apenas para esta função de qualquer maneira) ...

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

Então. Como posso deixar o usuário sair do loop?

Além disso, não quero usar a interrupção do teclado, porque o script precisa continuar a ser executado após o loop while terminar.

Chris
fonte

Respostas:

143

A maneira mais fácil é apenas interrompê-lo com o usual Ctrl-C(SIGINT).

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

Já que as Ctrl-Ccausas KeyboardInterruptsão levantadas, apenas pegue-o fora do loop e ignore-o.

Keith
fonte
2
@Chris: por que você não tenta. (e depois o comentário)
SilentGhost 01 de
Este travamento (recebo rastreamento de erro) ^Cé emitido enquanto em do_something(). Como voce pode evitar isso?
Atcold de
Meu do_something()lê alguns valores do USB, portanto, se ^Cfor emitido enquanto estou dentro do_something(), recebo erros de comunicação desagradáveis. Em vez disso, se estou dentro while, fora do do_something(), tudo está tranquilo. Então, eu queria saber como lidar com essa situação. Não tenho certeza se fui claro o suficiente.
Atcold
@Atcold Então você tem um módulo de extensão compilado que está usando. Que tipo de módulo é? É uma biblioteca C comum sendo encapsulada?
Keith
Tenho uma chamada para pyVISAe uma chamada para matplotlib, para que possa ter uma visualização ao vivo das minhas medições. E às vezes recebo erros funky. Acho que devo abrir uma pergunta separada e parar de poluir a sua resposta ...
Em
34

Existe uma solução que não requer módulos fora do padrão e é 100% transportável

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

fonte
4
Apenas uma nota para aqueles que usam Python 3+: raw_input () foi renomeado para input (), e o módulo thread agora é _thread.
Wieschie
Não funcionou no python 3, de acordo com a documentação do python 3: "Threads interagem estranhamente com interrupções: a exceção KeyboardInterrupt será recebida por um thread arbitrário. (Quando o módulo de sinal está disponível, as interrupções sempre vão para o thread principal.)"
Towhid
@Towhid Mas isso não usa interrupções. Ele usa a leitura de stdin.
Artyer
@Artyer Se não estou enganado, todas as teclas pressionadas geram interrupções, já que são geradas por um hardware. este código funcionou para você e, em caso afirmativo, você fez alguma alteração específica?
Towhid
2
@Towhid apenas thread-> _threade raw_input-> input. Você tem que pressionar enter para alimentar a linha. Se você quiser fazer em qualquer tecla, use getch .
Artyer
14

o código a seguir funciona para mim. Requer openCV (import cv2).

O código é composto por um loop infinito que está continuamente procurando uma tecla pressionada. Neste caso, quando a tecla 'q' é pressionada, o programa termina. Outras teclas podem ser pressionadas (neste exemplo 'b' ou 'k') para realizar ações diferentes, como alterar um valor de variável ou executar uma função.

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 josé
fonte
5
Bom, mas o CV2 é muito pesado, a menos que você já o esteja usando para outra coisa.
ogurets
1
porque AND com 255
Talespin_Kit
@Talespin_Kit & 0xff ”mascara a variável de modo que deixa apenas o valor nos últimos 8 bits e ignora todos os demais bits. Basicamente, ele garante que o resultado estará entre 0-255. Observe que eu nunca faço isso no opencv e as coisas funcionam bem.
Eric
6

Para Python 3.7, copiei e alterei a ótima resposta do usuário 297171 para que funcione em todos os cenários do Python 3.7 que testei.

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
fonte
Não sei se estou fazendo algo errado ou o quê, mas não consigo descobrir como parar esse loop. Como você faz isso?
Mihkel de
@Mihkel você deve pressionar a tecla <Enter>. Isso fará com que o loop seja encerrado.
rayzinnz de
Isso é decente, mas não generaliza para outras teclas além de enter.
John Forbes
não funciona para mim em python2.7, mas funciona em python3
crazjo
fazer multithreading é o que está em minha mente também, mas eu gosto bastante da resposta de @Keith acima. Simples e claro o suficiente.
viciado em
1

Sempre existe sys.exit().

A biblioteca do sistema na biblioteca central do Python tem uma função de saída que é muito útil durante a prototipagem. O código seria mais ou menos:

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
fonte
em python 3 raw_inputé substituído porinput
Talha Anwar
1

Modifiquei a resposta de rayzinnz para terminar o script com uma chave específica, neste caso a tecla 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
fonte
Olá! Embora este código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade de sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo à pergunta para os leitores no futuro, não apenas para a pessoa que está perguntando agora. Por favor edite sua resposta para adicionar explicações e dar uma indicação do que limitações e premissas se aplicam.
Brian
1

Seguindo este tópico pela toca do coelho, cheguei a isso, funciona no Win10 e no Ubuntu 20.04. Eu queria mais do que apenas matar o script e usar chaves específicas, e ele tinha que funcionar tanto no MS quanto no 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
fonte
0

Isso pode ser útil instalar pynput com - 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
fonte
0

Esta é a solução que encontrei com threads e bibliotecas padrão O

Loop continua até que uma tecla seja pressionada
Retorna a tecla pressionada como uma única string de caracteres

Funciona no 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()
Berni Gf
fonte
-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 entrar use 'ENTER'

Taimoor Arif
fonte