Leia a entrada de Raw_input () sem ter o prompt sobrescrito por outros threads em Python

StackOverflow https://stackoverflow.com/questions/2082387

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? :)

Foi útil?

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)

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

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top