如何用按键终止while循环?

126

我正在使用while循环读取串行数据并将其写入CSV文件。我希望用户能够在收集足够的数据后终止while循环。

while True:
    #do a bunch of serial stuff

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

我使用了OpenCV做过类似的事情,但它似乎在这个应用中不起作用(而且我真的不想仅仅为了这个函数导入OpenCV)...

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

那么,我该如何让用户跳出循环呢?

另外,我不想使用键盘中断,因为脚本需要在 while 循环终止后继续运行。

20个回答

190

最简单的方法是使用通常的Ctrl-C (SIGINT) 进行中断。

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

由于Ctrl-C会导致触发KeyboardInterrupt,所以只需在循环外捕获并忽略即可。


2
@Chris:为什么不试一试呢?(然后评论) - SilentGhost
1
这个程序崩溃了(我得到了错误追踪),是在do_something()中发出了^C。你怎么避免这种情况? - Atcold
2
我的 do_something() 从 USB 中读取一些值,所以如果在我正在执行 do_something() 的时候输入 ^C,会导致通信错误。相反,如果我在 do_something() 外面的 while 循环中,一切都很顺利。因此,我想知道如何处理这种情况。我不确定我表达清楚了。 - Atcold
1
@Atcold 所以你有一个编译好的扩展模块在使用。这是什么样的模块?它是常见的C库吗? - Keith
1
我有一个调用 pyVISA 和一个调用 matplotlib 的代码,这样我就可以实时可视化我的测量结果。但有时会出现一些奇怪的错误。我想我应该开一个单独的问题并停止污染你的答案... - Atcold
显示剩余2条评论

48

有一种解决方案,不需要任何非标准模块,而且100%可移植:

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

7
针对使用Python 3+的用户,请注意:raw_input()已经更名为input(),thread模块现在改为_thread。 - Wieschie
@Towhid 但是这并不使用中断。它使用从标准输入读取。 - Artyer
1
@Artyer 如果我没记错的话,所有按键都会引发中断,因为它们是由硬件引发的。这段代码对你有用吗?如果有,你做了什么具体的更改吗? - Towhid
2
@Towhid 只需将 thread -> _threadraw_input -> input。您需要按 Enter 键来输入命令行。如果您想在任何键上执行操作,请使用 getch - Artyer
我和楼主遇到了相同的情况,这个解决方案在Python 3中完美运行,而不必像“被接受的答案”建议的那样将所有内容都包装在try/except循环中等待KeyboardInterrupt。 - Steven Teglman
显示剩余2条评论

18

以下代码适用于我。它需要 openCV (import cv2)。

该代码由一个无限循环组成,不断地寻找按键。在这种情况下,当按下 'q' 键时,程序结束。可以按下其他键(在此示例中为 'b' 或 'k')来执行不同的操作,例如更改变量值或执行函数。

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

8
好的,但是cv2太重了,除非你已经在使用它做其他事情。 - ogurets
1
为什么要使用 AND 255? - Talespin_Kit
@Talespin_Kit & 0xff”掩码变量,只留下最后8位的值,忽略所有其他位。基本上它确保结果将在0-255之间。请注意,我从不在opencv中这样做,但结果仍然良好。 - eric

14

对于Python 3.7,我复制并修改了用户297171的非常好的答案,使它在我测试的所有Python 3.7场景中都可以正常工作。

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

我不知道我是否做错了什么,但我无法弄清楚如何停止这个循环?你怎么做到的? - Mihkel
@Mihkel你需要按下<Enter>键。这将导致循环退出。 - rayzinnz
1
这还不错,但不能推广到除回车键以外的键。 - John Forbes
在Python2.7上对我不起作用,但在Python3上可以。 - crazjo
做多线程也是我考虑的,但我还是挺喜欢 @Keith 在上面给出的答案。简单而清晰。 - addicted
input() 不是一个按键!它实际上是“输入一些内容然后按回车键”... - László Báthory

13
pip install keyboard

import keyboard

while True:
    # do something
    if keyboard.is_pressed("q"):
        print("q pressed, ending loop")
        break

3
你好新用户,感谢你的回答。你可以添加一些更详细的解释来说明这个方法是如何工作的,并提供必要的支持文档链接。仅仅贴出代码并不总是有帮助的,描述解决方案将有助于未来读者了解你的答案是否适合他们。 - PeterS
我认为这是原始问题的正确且最简单的解决方案。在Windows 10上使用Python 3.8测试通过。 - rhody
3
这个在*nix系统中不起作用,除非用户是root(也就是从来没有)。 - László Báthory

5

这里是我的解决方案,从这里和其他地方的帖子中获得了一些想法。循环不会在按下定义的键(abortKey)之前结束。循环尽可能快地停止并且不试图运行到下一个迭代。

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

4

2

通过跟随这个线程深入了解,我得出了这个结论,它适用于Win10和Ubuntu 20.04。我想要的不仅仅是杀死脚本,还要使用特定的键,并且它必须在MS和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()

1

总是可以使用sys.exit()

Python核心库中的系统库有一个退出函数,用于原型设计非常方便。 代码大致如下:

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

2
在Python 3中,raw_input被替换为input - Talha Anwar

1

对我来说,使用KeyboardInterrupt的被接受答案不可靠,但是以下解决方案使用pyinput可以工作(Python 3.10,Linux)。当按下q时,while循环结束:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接