2010-01-17 25 views
11

Estoy tratando de dejar que el usuario ingrese comandos en una consola usando raw_input(), esto funciona bien. El problema es que tengo hilos de fondo que ocasionalmente dan salida a la información de registro en la pantalla y cuando lo hacen, estropean la solicitud de entrada (ya que la salida va donde sea que esté el cursor en este momento).Lectura de entrada desde raw_input() sin que el aviso sea sobrescrito por otros hilos en Python

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 lo que podría ser similar al ejecutarlo:

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 el indicador se mueva junto con la salida del hilo. De la misma manera:

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

Respuesta

23

Recientemente encontré este problema, y ​​me gustaría dejar esta solución aquí para referencia futura. Estas soluciones borran el texto pendiente de entrada (lectura) del terminal, imprimen el nuevo texto y luego vuelven a imprimir en el terminal lo que había en el búfer raw_input.

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

Salida:

$ ./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 usa bien 2 o más líneas de búfer, pero tiene más dependencias de módulos (estándar) y requiere un poquito de hacking de 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('> ') 

Salida. líneas de readline anteriores borran 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:

How to get Linux console window width in Python

apt like column output - python library (este ejemplo de código se muestra cómo obtener el ancho de terminal, ya sea para Unix o Windows)

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

+0

Esto es exactamente lo que estaba buscando. Gracias :) – Jim

+1

['bendiciones' módulo] (https://pypi.python.org/pypi/blessings/) permite formatear la salida y moverse sin llegar demasiado profundo en las entrañas de la terminal. – jfs

+0

Tenga cuidado, en algunas versiones Python esto se rompe si el terminal se redimensiona durante el tiempo de ejecución debido a un error en el módulo 'readline' que hace que ignore los eventos de cambio de tamaño del terminal (por lo que no cambia el tamaño de su búfer interno en consecuencia y esto rompe la columna -contabilidad lógica). Ver https://bugs.python.org/issue23735. Esto parece estar corregido en Python 3.5, afortunadamente. Respuesta kick-ass de lo contrario :) – Thomas

0

No creo que sea posible. ¿Cómo debería comportarse de todos modos? No aparece nada hasta que el usuario presiona Entrar? Si eso es así, la salida solo vendrá cuando el usuario emita un comando (o lo que su sistema espera), y eso no parece deseable.

Me parece que los hilos deberían salir a otro archivo.

+0

No, quiero que los mensajes aparezcan antes de que el usuario presione enter. Pero el mensaje simplemente se movería hacia abajo para que no se sobrescriba con los mensajes. – Jim

3

Creo que necesita algo que le permita imprimir/eliminar/sobrescribir texto dinámicamente desde la ventana del terminal, p. cómo funcionan los comandos UNIX watch o top.

Creo que en su caso imprimiría "Prompt>", pero luego cuando obtenga un "Hello World" sobrescribirá "Prompt>" con "Hello World", y luego imprimirá "Prompt>" en la línea siguiente. No creo que puedas hacer eso con la impresión de salida regular en la terminal.

Es posible que pueda hacer lo que quiera usando la biblioteca curses de Python. Nunca lo he usado así que no puedo decirte cómo resolver tu problema (o si el módulo incluso será capaz de resolver tu problema), pero creo que vale la pena echarle un vistazo. Una búsqueda de "tutorial de maldiciones de pitón" proporcionó un PDF tutorial document que parece útil.

+0

Preferiría no tener dependencias adicionales. Pero dado que esto es solo un cambio cosmético. Creo que echaré un vistazo a las maldiciones y me limitaré al comportamiento actual si no existe :) – Jim

1

necesita actualizar stdout desde un solo subproceso, no desde múltiples subprocesos ... o si no tiene control sobre el interbloqueo de E/S.

querrá crear un único hilo para la escritura de salida.

puede utilizar una cola en el hilo y hacer que todos los otros hilos le escriban su información de registro de salida ... luego lea de esta cola y escriba a stdout en el momento apropiado junto con su mensaje de solicitud.

+1

La salida proveniente de los otros hilos se maneja de manera consistente y no se entrelaza entre sí. Es la entrada que es el problema. Usando una cola, y un solo hilo para entrada y salida, creo que tendría que implementar mi propio manejo de entrada. raw_input actualmente bloquea el hilo activo hasta EOL. – Jim

Cuestiones relacionadas