Leia a entrada de Raw_input () sem ter o prompt sobrescrito por outros threads em Python
-
21-09-2019 - |
Pergunta
Estou tentando deixar os comandos de entrada do usuário em um console usando raw_input (), isso funciona bem. O problema é que eu tenho threads de fundo que ocasionalmente produzem informações de log para a tela e, quando o fazem, eles atrapalham o prompt de entrada (já que a saída vai onde quer que o cursor esteja no momento).
Este é um pequeno programa Python que ilustra o que quero dizer.
#!/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
Este é um exemplo de como poderia ser quando eu o executo:
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
O que eu quero é que o prompt se mova junto com a saída do thread. Igual a:
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>
Alguma idéia de como conseguir isso sem recorrer a hacks feios? :)
Solução
Recentemente, encontrei esse problema e gostaria de deixar esta solução aqui para referência futura. Essas soluções limpam o texto pendente RAW_Input (ReadLine) do terminal, imprimem o novo texto e depois reimprimem o terminal o que estava no buffer RAW_Input.
Este primeiro programa é bem simples, mas só funciona corretamente quando há apenas 1 linha de texto esperando por 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('> ')
Resultado:
$ ./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,
O segundo lida corretamente por 2 ou mais linhas tamponadas, mas possui mais dependências (padrão) do módulo e requer um pouquinho de hacker em terminal:
#!/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('> ')
Resultado. Linhas de leitura anteriores limpadas corretamente:
$ ./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,
Fontes úteis:
Como obter a largura da janela do console do Linux no Python
saída de coluna APT - Biblioteca Python(Esta amostra de código mostra como obter a largura do terminal para Unix ou Windows)
Outras dicas
Eu acho que você precisa de algo que permita imprimir/excluir/substituir dinamicamente o texto da janela do terminal, por exemplo, como o Unix watch
ou top
comandos funcionam.
Eu acho que, no seu caso, você imprimiria "Prompt>", mas quando você obtém um "Hello World", substitui o prompt "" com "Hello World" e, em seguida, imprima "Prompt>" na linha abaixo. Eu não acho que você possa fazer isso com impressão regular de saída para o terminal.
Você pode ser capaz de fazer o que quiser usando o Python maldições biblioteca. Eu nunca o usei, por isso não posso dizer como resolver seu problema (ou se o módulo será capaz de resolver seu problema), mas acho que vale a pena dar uma olhada. Uma busca por "tutorial de maldições de python" forneceu um Documento do tutorial em PDF o que parece útil.
Você precisa atualizar o stdout de um único thread, não de vários threads ... ou então você não tem controle sobre a E/S intercalada.
Você deseja criar um único thread para a gravação de saída.
Você pode usar uma fila no thread e fazer com que todos os outros threads escrevam suas informações de registro de saída para ele. Em seguida, leia nesta fila e escreva no stdout nos momentos apropriados, juntamente com sua mensagem rápida.
Eu não acho que seja possível. Como isso deve se comportar de qualquer maneira? Nada aparece até que o usuário pressione Enter? Se é assim, a saída só ocorreria quando o usuário emite um comando (ou o que seu sistema espera), e isso não parece desejável.
Meths seus threads devem ser lançados para outro arquivo.