如何在循环中非阻塞地获取用户输入

2 投票
3 回答
11685 浏览
提问于 2025-04-15 13:31

我正在尝试写一个循环,这个循环会不断更新屏幕,方法是使用 os.system("clear") 来清屏,然后每隔几秒打印出不同的文本信息。可是我想在这个循环中获取用户输入,像 raw_input() 这样的函数会让程序暂停并等待输入,这不是我想要的功能。

import os
import time

string = "the fox jumped over the lazy dog"
words = string.split(" ")
i = 0 

while 1:
    os.system("clear")
    print words[i]
    time.sleep(1)
    i += 1
    i = i%len(words)

我希望能够在循环中按 'q' 来退出,或者按 'p' 来暂停。

3 个回答

2

你可以通过线程来实现这个功能,下面是一个简单的例子:

import threading, os, time, itertools, Queue

# setting a cross platform getch like function
# thks to the Python Cookbook
# why isn't standard on this battery included language ?
try : # on windows
    from msvcrt import getch
except ImportError : # on unix like systems
    import sys, tty, termios
    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

# this will allow us to communicate between the two threads
# Queue is a FIFO list, the param is the size limit, 0 for infinite
commands = Queue.Queue(0)

# the thread reading the command from the user input     
def control(commands) :

    while 1 :

        command = getch()
        commands.put(command) # put the command in the queue so the other thread can read it

        #  don't forget to quit here as well, or you will have memory leaks
        if command == "q" :
            break


# your function displaying the words in an infinite loop
def display(commands):

    string = "the fox jumped over the lazy dog"
    words = string.split(" ")
    pause = False 
    command = ""

    # we create an infinite generator from you list
    # much better than using indices
    word_list = itertools.cycle(words) 

    # BTW, in Python itertools is your best friend

    while 1 :

        # parsing the command queue
        try:
           # false means "do not block the thread if the queue is empty"
           # a second parameter can set a millisecond time out
           command = commands.get(False) 
        except Queue.Empty, e:
           command = ""

        # behave according to the command
        if command == "q" :
            break

        if command == "p" :
            pause = True

        if command == "r" :
            pause = False

        # if pause is set to off, then print the word
        # your initial code, rewritten with a generator
        if not pause :
            os.system("clear")
            print word_list.next() # getting the next item from the infinite generator 

        # wait anyway for a second, you can tweak that
        time.sleep(1)



# then start the two threads
displayer = threading.Thread(None, # always to None since the ThreadGroup class is not implemented yet
                            display, # the function the thread will run
                            None, # doo, don't remember and too lazy to look in the doc
                            (commands,), # *args to pass to the function
                             {}) # **kwargs to pass to the function

controler = threading.Thread(None, control, None, (commands,), {})

if __name__ == "__main__" :
    displayer.start()
    controler.start()

像往常一样,使用线程是有点复杂的,所以在开始编写代码之前,一定要确保你理解你在做什么。

注意:在Python 3中,Queue会被改名为queue。

3

你还可以看看这里的一个示例,它提供了你需要的功能,适用于Unix和Windows系统。

9

Python的标准库里有一个叫做 select 的模块,可能正是你需要的。标准输入的文件描述符是0,不过在类Unix系统上,你可能还需要把终端设置为“原始”模式(也就是和“熟食”模式相对),这样才能获取单个按键的输入,而不是整行带换行符的输入。如果你是在Windows系统上,msvcrt模块同样在Python标准库里,提供了你需要的所有功能。使用 msvcrt.kbhit() 可以检查是否有按键被按下,如果有的话,msvcrt.getch() 就可以告诉你按下的是哪个字符。

撰写回答