在Python中从raw_input()读取输入而不被其他线程覆盖提示
我正在尝试让用户在控制台输入命令,使用的是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 个回答
你需要从一个线程来更新输出,而不是多个线程一起更新……否则你就无法控制输出信息的交错。
你应该创建一个专门负责输出的线程。
你可以在这个线程里使用一个队列,让其他所有线程把它们的输出信息写到这个队列里……然后在合适的时候从这个队列里读取信息,并把它们写到输出上,连同你的提示信息一起。
我觉得你需要一种可以动态地在终端窗口中打印、删除或覆盖文本的工具,比如UNIX系统中的watch
或top
命令的工作方式。
在你的情况下,你可以先打印“Prompt>”,然后当你收到“Hello World”时,就用“Hello World”覆盖掉“Prompt>”,接着在下面的行再打印“Prompt>”。我觉得用普通的输出方式在终端上是做不到这一点的。
你可能可以使用Python的curses库来实现你想要的功能。我自己没有用过这个库,所以不能告诉你怎么解决你的问题(或者这个模块是否能解决你的问题),但我觉得值得一试。搜索“python curses tutorial”可以找到一个PDF教程文档,看起来很有帮助。
我最近遇到了这个问题,想把解决方案留在这里,以备将来参考。这些解决方案的作用是清除终端中等待输入的文本,打印新的文本,然后再把原本在输入缓冲区里的内容重新打印到终端上。
第一个程序比较简单,但只有在等待输入的文本只有一行时才能正确工作:
#!/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,
有用的资源:
类似apt的列输出 - Python库 (这个代码示例展示了如何获取Unix或Windows的终端宽度)