Lectura de la entrada de RAW_INPUT () sin tener el aviso sobrescribido por otros hilos en Python

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

Pregunta

Estoy tratando de dejar que los comandos de entrada del usuario en una consola usando raw_input (), esto funciona bien. El problema es que tengo subprocesos de fondo que ocasionalmente emiten la información de registro a la pantalla y cuando lo hacen arruinan el indicador de entrada (ya que la salida va donde el cursor esté en este momento).

Este es un pequeño programa de Python que ilustra lo que quiero decir.

#!/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 es un ejemplo de cómo podría verse cuando lo ejecuto:

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

Lo que quiero es que la solicitud se mueva junto con la salida del hilo. Al igual que:

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> 

¿Alguna idea sobre cómo lograr esto sin recurrir a hacks feos? :)

¿Fue útil?

Solución

Recientemente encontré este problema y me gustaría dejar esta solución aquí para referencia futura. Estas soluciones borran el texto pendiente de RAW_Input (Readline) del terminal, imprime el nuevo texto, luego reimprima al terminal lo que estaba en el búfer RAW_Input.

Este primer programa es bastante simple, pero solo funciona correctamente cuando solo hay 1 línea de texto esperando 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('> ')

Producción:

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

El segundo maneja correctamente 2 o más líneas tamponadas, pero tiene más dependencias (estándar) del módulo y requiere un poco de hackería 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('> ')

Producción. Las líneas de lectura anteriores se borraron correctamente:

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

Fuentes útiles:

Cómo obtener el ancho de la ventana de la consola de Linux en Python

APT como salida de columna - Biblioteca de Python(Esta muestra de código muestra cómo obtener el ancho del terminal para UNIX o Windows)

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

Otros consejos

Creo que necesitas algo que te permita imprimir dinámicamente/eliminar/sobrescribir el texto desde la ventana del terminal, por ejemplo, cómo el Unix watch o top Los comandos funcionan.

Creo que en su caso imprimiría "indicador>", pero luego, cuando obtenga un "Hello World", sobrescribe "Aviso>" con "Hello World", y luego imprima "Aviso>" en la línea a continuación. No creo que pueda hacer eso con la impresión de salida regular al terminal.

Es posible que puedas hacer lo que quieras usando Python's maldiciones biblioteca. Nunca lo he usado, así que no puedo decirte cómo resolver tu problema (o si el módulo incluso podrá resolver tu problema), pero creo que vale la pena echarle un vistazo. Una búsqueda de "Tutorial de Python Curses" proporcionó un Documento de tutorial PDF Lo cual parece útil.

Debe actualizar STDOUT desde un solo hilo, no de varios hilos ... o de lo contrario no tiene control sobre las E/S entrelazadas.

Querrá crear un solo hilo para la escritura de salida.

Puede usar una cola en el hilo y hacer que todos los demás hilos escriban su información de registro de salida. Luego lea desde esta cola y escriba en stdout en los momentos apropiados junto con su mensaje rápido.

No creo que sea posible. ¿Cómo debería comportarse de todos modos? ¿No aparece nada hasta que el usuario presione ingrese? Si es así, la salida solo se produciría cuando el usuario emita un comando (o lo que sea que su sistema espere), y eso no suena deseable.

Comfortas que sus hilos deben emitir a otro archivo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top