在Python中从raw_input()读取输入而不被其他线程覆盖提示

12 投票
4 回答
12951 浏览
提问于 2025-04-15 18:08

我正在尝试让用户在控制台输入命令,使用的是raw_input(),这个功能运行得很好。不过问题是,我有一些后台线程,它们偶尔会在屏幕上输出日志信息,而这些输出会搞乱输入提示符的位置(因为输出会出现在光标当前的位置)。

这是一个小的Python程序,能说明我的意思。

#!/usr/bin/env python
import threading
import time

def message_loop():
    while True:
        time.sleep(1)
        print "Hello World"

thread = threading.Thread(target = message_loop)
thread.start()

while True:
    input = raw_input("Prompt> ")
    print "You typed", input

这是我运行它时可能出现的样子:

Prompt> Hello World
Hello World
Hello World
Hello World
test
You typed test
Prompt> Hello World
Hello World
Hello World
hellHello World
o
You typed hello
Prompt> Hello World
Hello World
Hello World
Hello World

我希望的是,提示符能够随着线程的输出一起移动。像这样:

Hello World
Hello World
Prompt> test
You typed test
Hello World
Hello World
Hello World
Hello World
Hello World
Prompt> hello
You typed hello
Hello World
Hello World
Hello World
Hello World
Prompt> 

有没有什么好的办法可以做到这一点,而不需要使用一些很丑陋的黑科技呢?:)

4 个回答

1

你需要从一个线程来更新输出,而不是多个线程一起更新……否则你就无法控制输出信息的交错。

你应该创建一个专门负责输出的线程。

你可以在这个线程里使用一个队列,让其他所有线程把它们的输出信息写到这个队列里……然后在合适的时候从这个队列里读取信息,并把它们写到输出上,连同你的提示信息一起。

3

我觉得你需要一种可以动态地在终端窗口中打印、删除或覆盖文本的工具,比如UNIX系统中的watchtop命令的工作方式。

在你的情况下,你可以先打印“Prompt>”,然后当你收到“Hello World”时,就用“Hello World”覆盖掉“Prompt>”,接着在下面的行再打印“Prompt>”。我觉得用普通的输出方式在终端上是做不到这一点的。

你可能可以使用Python的curses库来实现你想要的功能。我自己没有用过这个库,所以不能告诉你怎么解决你的问题(或者这个模块是否能解决你的问题),但我觉得值得一试。搜索“python curses tutorial”可以找到一个PDF教程文档,看起来很有帮助。

26

我最近遇到了这个问题,想把解决方案留在这里,以备将来参考。这些解决方案的作用是清除终端中等待输入的文本,打印新的文本,然后再把原本在输入缓冲区里的内容重新打印到终端上。

第一个程序比较简单,但只有在等待输入的文本只有一行时才能正确工作:

#!/usr/bin/python

import time,readline,thread,sys

def noisy_thread():
    while True:
        time.sleep(3)
        sys.stdout.write('\r'+' '*(len(readline.get_line_buffer())+2)+'\r')
        print 'Interrupting text!'
        sys.stdout.write('> ' + readline.get_line_buffer())
        sys.stdout.flush()

thread.start_new_thread(noisy_thread, ())
while True:
    s = raw_input('> ')

输出:

$ ./threads_input.py
Interrupting text!
Interrupting text!
Interrupting text!
> WELL, PRINCE, Genoa and Lucca are now no more than private estates of the Bo
Interrupting text!
> WELL, PRINCE, Genoa and Lucca are now no more than private estates of the Bo
naparte family. No, I warn you, that if you do not tell me we are at war,

第二个程序可以正确处理两行或更多的缓冲文本,但它依赖于更多的标准模块,并且需要稍微调整一下终端设置:

#!/usr/bin/python

import time,readline,thread
import sys,struct,fcntl,termios

def blank_current_readline():
    # Next line said to be reasonably portable for various Unixes
    (rows,cols) = struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,'1234'))

    text_len = len(readline.get_line_buffer())+2

    # ANSI escape sequences (All VT100 except ESC[0G)
    sys.stdout.write('\x1b[2K')                         # Clear current line
    sys.stdout.write('\x1b[1A\x1b[2K'*(text_len/cols))  # Move cursor up and clear line
    sys.stdout.write('\x1b[0G')                         # Move to start of line


def noisy_thread():
    while True:
        time.sleep(3)
        blank_current_readline()
        print 'Interrupting text!'
        sys.stdout.write('> ' + readline.get_line_buffer())
        sys.stdout.flush()          # Needed or text doesn't show until a key is pressed


if __name__ == '__main__':
    thread.start_new_thread(noisy_thread, ())
    while True:
        s = raw_input('> ')

输出。之前的输入行被正确清除:

$ ./threads_input2.py
Interrupting text!
Interrupting text!
Interrupting text!
Interrupting text!
> WELL, PRINCE, Genoa and Lucca are now no more than private estates of the Bo
naparte family. No, I warn you, that if you do not tell me we are at war,

有用的资源:

如何在Python中获取Linux控制台窗口宽度

类似apt的列输出 - Python库 (这个代码示例展示了如何获取Unix或Windows的终端宽度)

http://en.wikipedia.org/wiki/ANSI_escape_code

撰写回答