Чтение ввода из raw_input() без приглашения, перезаписанного другими потоками в Python

Я пытаюсь позволить пользователю вводить команды на консоли с помощью 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> 

Любые идеи о том, как достичь этого, не прибегая к уродливым взломам?:)

Ответ 1

Недавно я столкнулся с этой проблемой и хотел бы оставить это решение здесь для дальнейшего использования. Эти решения очищают ожидающий текст raw_input (readline) от терминала, распечатывают новый текст и затем перепечатывают на терминал то, что было в буфере raw_input.

Эта первая программа довольно проста, но работает только правильно, когда есть только 1 строка текста, ожидающая raw_input:

#!/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,

Вторая правильно обрабатывает 2 или более буферизованные строки, но имеет больше (стандартных) зависимостей модуля и требует бит-бит терминального хакера:

#!/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('> ')

Выход. Предыдущие строки readline очищены должным образом:

$ ./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,

Полезные источники:

Как получить ширину окна консоли Linux в Python

apt как вывод столбца - библиотека python (В этом примере кода показано, как получить ширину терминала для Unix или Windows)

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

Ответ 2

Я думаю, вам нужно что-то, что позволяет динамически печатать/удалять/перезаписывать текст из окна терминала, например. как работают UNIX watch или top.

Я думаю, что в вашем случае вы бы напечатали "Prompt > " , но затем, когда вы получите "Hello World", вы перезаписываете "Prompt > " с "Hello World", а затем печатаете "Prompt > " в строке ниже. Я не думаю, что вы можете сделать это с обычной печатью вывода на терминал.

Возможно, вы сможете сделать то, что хотите, используя библиотеку Python curses. Я никогда не использовал его, поэтому я не могу сказать вам, как решить вашу проблему (или если модуль сможет решить вашу проблему), но я думаю, что стоит взглянуть на это. Поиск "учебника python curses" предоставил учебник PDF, который кажется полезным.

Ответ 3

вам нужно обновить stdout из одного потока, а не из нескольких потоков... или иначе у вас нет контроля над чередованием ввода-вывода.

вам нужно создать один поток для вывода.

вы можете использовать Очередь в потоке и все остальные потоки записывать в нее свою информацию о протоколе вывода. Затем прочитайте с этой очереди и напишите на stdout в соответствующее время вместе с вашим приглашением.

Ответ 4

Я не думаю, что это возможно. Как это должно вести себя так или иначе? Пока ничего не появляется, пока пользователь не нажмет Enter? Если это так, вывод будет появляться только тогда, когда пользователь выдает команду (или независимо от того, что ожидает ваша система), и это не кажется желательным.

Определяет, что ваши потоки должны выводиться в другой файл.