【问题标题】:How to kill a while loop with a keystroke?如何用击键杀死while循环?
【发布时间】:2012-10-22 06:26:47
【问题描述】:

我正在使用 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循环终止后继续运行。

【问题讨论】:

    标签: python while-loop break


    【解决方案1】:
    from time import sleep
    from threading import Thread
    import threading
    
    stop_flag = 0
        
        def Wait_Char():
            global stop_flag
            v = input("Enter Char")
            if(v == "z"):
                stop_flag = 1
        
        
        def h():
            while(True):
                print("Hello Feto")
                time.sleep(1)
                if(stop_flag == 1):
                    break
        
        
        thread1 = Thread(target=Wait_Char)
        thread2 = Thread(target=h)
        thread1.start()
        thread2.start()
        print("threads finished...exiting")
    

    这不是最好的方法,但它可以完成您想要的工作
    运行 2 个线程 一个等待您要使用
    (Wait_Char 方法)
    停止循环的键 还有一个 for 循环
    (H 方法)
    并且两者都看到了一个控制停止过程的全局变量 stop_flag 按z时停止

    【讨论】:

      【解决方案2】:

      有一种解决方案不需要非标准模块并且 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()
      

      【讨论】:

      • 对于那些使用 Python 3+ 的用户请注意:raw_input() 已重命名为 input(),线程模块现在是 _thread。
      • 在 python 3 中不起作用,根据 python 3 文档:“线程与中断的交互奇怪:任意线程将接收到 KeyboardInterrupt 异常。(当信号模块可用时,中断总是转到主线程。)”
      • @Towhid 但这不使用中断。它使用从标准输入读取。
      • @Artyer 如果我没记错的话,所有击键都会引发中断,因为它们是由硬件引发的。这段代码对您有用吗?如果是,您是否进行了任何具体更改?
      • @Towhid 只是 thread -> _threadraw_input -> input。您必须按回车键才能换行。如果您想对任何键执行操作,请使用getch
      【解决方案3】:

      这里是另一个使用threading.Event 的示例,无需捕获SIGINT (Ctrl+c)。

      正如@Atcold 在接受答案下方的评论中提到的那样,在循环中按Ctrl+c 可能会中断长时间运行的操作并使其处于未定义状态。当长时间运行的操作来自您正在调用的库时,这会特别烦人。

      在下面的示例中,用户需要先按q,然后再按Enter。如果你想立即捕捉击键,你需要像_Getch() from this answer 这样的东西。

      import time
      from threading import Thread, Event
      
      
      def read_input(q_entered_event):
          c = input()
          if c == "q":
              print("User entered q")
              q_entered_event.set()
      
      
      def do_long_running_stuff():
          q_pressed_event = Event()
          input_thread = Thread(target=read_input,
                                daemon=True,
                                args=(q_pressed_event,))
          input_thread.start()
          while True:
              print("I am working ...")
              time.sleep(1)
              if q_pressed_event.is_set():
                  break
          
          print("Process stopped by user.")
      
      
      if __name__  == "__main__":
          do_long_running_stuff()
      

      【讨论】:

        【解决方案4】:

        这是一个对我有用的解决方案。从这里和其他地方的帖子中得到一些想法。直到按下定义的键(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
        

        【讨论】:

          【解决方案5】:

          这是一个简单的 Windows 解决方案,可以安全地结束当前迭代然后退出。我将它与一个反例一起使用,该示例使用“Esc”键中断循环并退出。它使用来自msvcrt 包的kbhit()getch() 函数。时间包仅出于地役权原因(设置事件之间的时间延迟)而被调用。

          import msvcrt, time
          
          print("Press 'Esc' to stop the loop...")
          x = 0
          while True:
              x += 1
              time.sleep(0.5)
              print(x)
              
              if msvcrt.kbhit():
                  if msvcrt.getch() == b'\x1b':
                      print("You have pressed Esc! See you!")
                      time.sleep(2)    
                      break
          

          kbhit() 如果按键正在等待读取,则函数返回 True

          getch() 函数读取按键并将结果字符作为字节字符串返回。它可以与任何键一起使用

          b'\x1b' 是 'Esc' 键的字节串字符。

          【讨论】:

            【解决方案6】:
            pip install keyboard
            
            import keyboard
            
            while True:
                # do something
                if keyboard.is_pressed("q"):
                    print("q pressed, ending loop")
                    break
            

            【讨论】:

            • 您好新用户,感谢您的回答。您能否就其工作原理以及任何支持文档链接(如有必要)添加更多解释。仅粘贴代码并不总是有帮助,描述解决方案将帮助未来的读者了解您的答案是否适合他们。
            • 我认为这是对原始问题的正确且最简单的解决方案。在 windows 10,python 3.8 上工作
            • 这个在 *nix 系统中不起作用,除非用户是 root (又名从不)
            【解决方案7】:

            从跟踪这个线程到兔子洞,我来到了这个,适用于 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()
            

            【讨论】:

              【解决方案8】:

              这是我在线程和标准库中找到的解决方案

              循环一直持续到按下一个键
              将按下的键作为单个字符串返回

              适用于 Python 2.7 和 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()
              

              【讨论】:

                【解决方案9】:

                我修改了 rayzinnz 的答案以使用特定键结束脚本,在本例中为转义键

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

                【讨论】:

                • 您好!虽然这段代码可以解决问题,including an explanation 解决问题的方式和原因确实有助于提高帖子的质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提出问题的人。请edit您的回答添加解释并说明适用的限制和假设。
                【解决方案10】:
                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('  :( ')
                

                输入使用'ENTER'

                【讨论】:

                  【解决方案11】:

                  对于 Python 3.7,我复制并更改了 user297171 的非常好的答案,因此它适用于我测试的 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 你必须按 键。这将导致循环退出。
                  • 这很不错,但不能推广到除了 enter 之外的键。
                  • 在 python2.7 上不适用于我,但在 python3 上有效
                  • 做多线程也是我的想法,但我很喜欢@Keith 上面的回答。简单明了。
                  【解决方案12】:

                  这可能会有所帮助 使用 -- 安装 pynput 点安装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 
                  

                  【讨论】:

                    【解决方案13】:

                    总是有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()
                    

                    【讨论】:

                    • 在python 3中raw_inputinput替换
                    【解决方案14】:

                    以下代码适用于我。它需要 openCV(导入 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 ...
                    

                    【讨论】:

                    • 很好,但是 cv2 太重了,除非你已经将它用于其他用途。
                    • 为什么用 255 和
                    • @Talespin_Kit & 0xff” 屏蔽变量,因此它只保留最后 8 位中的值,并忽略所有其余位。基本上它确保结果将在 0-255 之间。请注意,我从来没有在 opencv 中这样做过,而且一切正常。
                    【解决方案15】:

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

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

                    由于Ctrl-C 导致KeyboardInterrupt 被引发,只需在循环外捕获它并忽略它。

                    【讨论】:

                    • @Chris:你为什么不试一试。 (然后评论)
                    • 这崩溃(我得到错误回溯)是在do_something() 中发出^C。如何避免这种情况?
                    • 我的 do_something() 从 USB 读取一些值,因此,如果在我在 do_something() 内部时发出 ^C,我会遇到严重的通信错误。相反,如果我在while 中,在do_something() 之外,一切都很顺利。所以,我想知道如何处理这种情况。我不确定我是否说得够清楚。
                    • @Atcold 所以你有一个你正在使用的编译扩展模块。它是什么样的模块?是不是一个通用的 C 库被包装了?
                    • 我已致电pyVISAmatplotlib,以便实时查看我的测量结果。我有时会遇到一些奇怪的错误。我想我应该打开一个单独的问题并停止污染你的答案......
                    【解决方案16】:

                    pyHook 可能会有所帮助。 http://sourceforge.net/apps/mediawiki/pyhook/index.php?title=PyHook_Tutorial#tocpyHook%5FTutorial4

                    查看键盘挂钩;这是更通用的——如果你想要特定的键盘交互而不仅仅是使用 KeyboardInterrupt。

                    此外,总的来说(取决于您的使用)我认为仍然可以使用 Ctrl-C 选项来终止您的脚本是有意义的。

                    另见上一个问题:Detect in python which keys are pressed

                    【讨论】:

                      猜你喜欢
                      • 2018-04-04
                      • 1970-01-01
                      • 2013-02-10
                      • 2021-01-05
                      • 1970-01-01
                      • 2016-01-26
                      • 1970-01-01
                      • 2012-11-29
                      • 2011-12-19
                      相关资源
                      最近更新 更多