Jak zabić pętlę while za pomocą naciśnięcia klawisza?

86

Czytam dane szeregowe i piszę do pliku csv za pomocą pętli while. Chcę, aby użytkownik mógł wyłączyć pętlę while, gdy poczuje, że zebrał wystarczającą ilość danych.

while True:
    #do a bunch of serial stuff

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

Zrobiłem coś takiego za pomocą opencv, ale wygląda na to, że nie działa w tej aplikacji (i tak naprawdę nie chcę importować opencv tylko dla tej funkcji) ...

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

Więc. Jak mogę pozwolić użytkownikowi wyrwać się z pętli?

Nie chcę też używać przerwania klawiatury, ponieważ skrypt musi nadal działać po zakończeniu pętli while.

Chris
źródło

Odpowiedzi:

143

Najłatwiej jest po prostu przerwać to zwykłym Ctrl-C(SIGINT).

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

Ponieważ Ctrl-Cprzyczyny KeyboardInterruptsą podnoszone, po prostu złap go poza pętlą i zignoruj.

Keith
źródło
2
@Chris: dlaczego nie spróbujesz. (a następnie komentarz)
SilentGhost
Ta awaria (otrzymuję śledzenie błędów) ^Cjest wydawana podczas do_something(). Jak możesz tego uniknąć?
Atcold
My do_something()odczytuje pewne wartości z USB, więc jeśli ^Cjest wydawane, gdy jestem w środku do_something()mam nieprzyjemnych błędów komunikacyjnych. Zamiast tego, jeśli jestem na whilezewnątrz do_something(), wszystko jest gładkie. Tak więc zastanawiałem się, jak sobie z tym poradzić. Nie jestem pewien, czy wyraziłem się wystarczająco jasno.
Atcold
@Atcold Masz więc skompilowany moduł rozszerzenia, którego używasz. Jaki to rodzaj modułu? Czy jest to pakowana wspólna biblioteka C?
Keith,
Mam telefon pyVISAi telefon matplotlib, aby mieć wizualizację moich pomiarów na żywo. I czasami dostaję dziwne błędy. Myślę, że powinienem otworzyć osobne pytanie i przestać zanieczyszczać twoją odpowiedź ...
Atcold
34

Istnieje rozwiązanie, które nie wymaga niestandardowych modułów i jest w 100% przenośne

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

źródło
4
Tylko uwaga dla tych, którzy używają Pythona 3+: raw_input () został przemianowany na input (), a moduł wątku to teraz _thread.
Wieschie
Nie działał w Pythonie 3, zgodnie z dokumentacją Pythona 3: "Wątki dziwnie oddziałują z przerwaniami: wyjątek KeyboardInterrupt zostanie odebrany przez dowolny wątek. (Gdy moduł sygnału jest dostępny, przerwania zawsze trafiają do głównego wątku)."
Towhid
@Towhid Ale to nie używa przerwań. Używa czytania ze stdin.
Artyer
@Artyer Jeśli się nie mylę, wszystkie naciśnięcia klawiszy powodują przerwania, ponieważ są wywoływane przez sprzęt. czy ten kod działał dla Ciebie, a jeśli tak, to czy wprowadziłeś jakieś konkretne zmiany?
Towhid
2
@Towhid tylko thread-> _threadi raw_input-> input. Musisz nacisnąć klawisz Enter, aby zasilić linię. Jeśli chcesz to zrobić na dowolnym klawiszu, użyj getch .
Artyer
14

następujący kod działa dla mnie. Wymaga openCV (import cv2).

Kod składa się z nieskończonej pętli, która nieustannie szuka naciśniętego klawisza. W tym przypadku naciśnięcie klawisza „q” powoduje zakończenie programu. Inne klawisze mogą być naciskane (w tym przykładzie „b” lub „k”), aby wykonać różne czynności, takie jak zmiana wartości zmiennej lub wykonanie funkcji.

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
źródło
5
Dobrze, ale cv2 jest za ciężkie, chyba że już go używasz do czegoś innego.
ogurets
1
dlaczego I z 255
Talespin_Kit
@Talespin_Kit & 0xff ”maskuje zmienną, więc pozostawia tylko wartość z ostatnich 8 bitów i ignoruje wszystkie pozostałe bity. Zasadniczo zapewnia, że ​​wynik będzie w zakresie 0-255. Uwaga: Nigdy nie robię tego w opencv i wszystko działa dobrze.
eric
6

W przypadku Pythona 3.7 skopiowałem i zmieniłem bardzo ładną odpowiedź użytkownika297171, więc działa ona we wszystkich scenariuszach w Pythonie 3.7, które testowałem.

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
źródło
Nie wiem, czy robię coś źle, czy co, ale nie wiem, jak zatrzymać tę pętlę? Jak to robisz?
Mihkel
@Mihkel musisz wcisnąć klawisz <Enter>. Spowoduje to zamknięcie pętli.
rayzinnz
Jest to przyzwoite, ale nie generalizuje kluczy innych niż enter.
John Forbes
nie działa dla mnie na pythonie2.7, ale działa na pythonie3
crazjo
myślę również o wielowątkowości, ale podoba mi się powyższa odpowiedź @Keith. Proste i wystarczająco jasne.
uzależniony
4

pyHook może pomóc. http://sourceforge.net/apps/mediawiki/pyhook/index.php?title=PyHook_Tutorial#tocpyHook%5FTutorial4

Zobacz zaczepy klawiatury; jest to bardziej uogólnione - jeśli chcesz mieć określone interakcje z klawiaturą, a nie tylko używać funkcji KeyboardInterrupt.

Ponadto ogólnie (w zależności od zastosowania) myślę, że opcja Ctrl-C nadal dostępna do zabicia skryptu ma sens.

Zobacz też poprzednie pytanie: Wykryj w Pythonie, które klawisze są naciskane

Anov
źródło
1

Jest zawsze sys.exit().

Biblioteka systemowa w podstawowej bibliotece Pythona ma funkcję wyjścia, która jest bardzo przydatna podczas tworzenia prototypów. Kod byłby podobny do:

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
źródło
w pythonie 3 raw_inputzastępuje sięinput
Talha Anwar
1

Zmodyfikowałem odpowiedź z rayzinnz, aby zakończyć skrypt określonym kluczem, w tym przypadku klawiszem 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
źródło
Cześć! Chociaż ten kod może rozwiązać problem, w tym wyjaśnienie, jak i dlaczego to rozwiązuje problem, naprawdę pomogłoby poprawić jakość twojego posta i prawdopodobnie zaowocowałoby większą liczbą pozytywnych głosów. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a nie tylko osoba, która zapyta teraz. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnień i dać wskazówkę co zastosować ograniczenia i założenia.
Brian
1

Od podążania tym wątkiem w dół króliczej nory doszedłem do tego, działa na Win10 i Ubuntu 20.04. Chciałem czegoś więcej niż tylko zabicia skryptu i użycia określonych kluczy, a to musiało działać zarówno w MS, jak i Linuksie.

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
źródło
0

Może to być pomocne podczas instalacji pynput z - 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
źródło
0

To jest rozwiązanie, które znalazłem z wątkami i standardowymi bibliotekami

Pętla działa do momentu naciśnięcia jednego klawisza
Zwraca klawisz wciśnięty jako pojedynczy ciąg znaków

Działa w Pythonie 2.7 i 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
źródło
-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('  :( ')

aby wejść użyj 'ENTER'

Taimoor Arif
źródło