2010-01-17 4 views
11

J'essaie de laisser l'utilisateur entrer des commandes sur une console en utilisant raw_input(), cela fonctionne très bien. Le problème est que j'ai des threads d'arrière-plan qui sortent occasionnellement des informations de logs à l'écran et quand ils le font, ils gâchent l'invite de saisie (puisque la sortie va là où le curseur se trouve pour le moment).Lire l'entrée de raw_input() sans avoir l'invite écrasée par d'autres threads en Python

Ceci est un petit programme Python qui illustre ce que je veux dire.

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

Voici un exemple de ce qu'il pourrait ressembler quand je le lance:

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 

Ce que je veux est l'invite à se déplacer en même temps que la sortie du fil. Comme si:

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> 

Toutes les idées sur la façon d'y parvenir sans avoir recours à des hacks laids? :)

Répondre

23

J'ai récemment rencontré ce problème, et je voudrais laisser cette solution ici pour référence future. Ces solutions effacent le texte raw_input (readline) en attente du terminal, impriment le nouveau texte, puis réimpriment sur le terminal ce qui se trouvait dans le tampon raw_input.

Ce premier programme est assez simple, mais ne fonctionne correctement quand il y a seulement 1 ligne de texte en attente 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('> ') 

Sortie:

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

Le second est capable de traiter 2 ou plus lignes tamponnées, mais a plus de dépendances de module (standard) et nécessite un petit peu de hackery 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('> ') 

Sortie. Lignes précédentes readline effacées correctement:

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

sources utiles:

How to get Linux console window width in Python

apt like column output - python library (Cet exemple de code montre comment obtenir la largeur du terminal pour UNIX ou Windows)

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

+0

C'est exactement ce que je cherchais. Merci :) – Jim

+1

[module 'bénédictions'] (https://pypi.python.org/pypi/blessings/) permet de formater la sortie et de se déplacer sans trop atteindre les entrailles du terminal. – jfs

+0

Attention, sur certaines versions de Python cela casse si le terminal est redimensionné pendant l'exécution en raison d'un bug dans le module 'readline' qui l'empêche d'ignorer les événements de redimensionnement du terminal (donc il ne redimensionne pas son buffer interne -counting logic). Voir https://bugs.python.org/issue23735. Cela semble être corrigé dans Python 3.5 heureusement. Kick-Ass répond autrement :) – Thomas

0

Je ne pense pas que ce soit possible. Comment cela devrait-il se comporter de toute façon? Rien ne s'affiche jusqu'à ce que l'utilisateur appuie sur Entrée? Si c'est le cas, la sortie ne viendrait que lorsque l'utilisateur émet une commande (ou ce que votre système attend), et cela ne semble pas souhaitable.

Je pense que vos threads doivent sortir dans un autre fichier.

+0

Non, je veux que les messages apparaissent avant que l'utilisateur appuie sur Entrée. Mais l'invite descendrait juste pour ne pas être écrasée par les messages. – Jim

3

Je pense que vous avez besoin de quelque chose qui vous permet d'imprimer/supprimer/remplacer dynamiquement du texte à partir de la fenêtre de terminal, par exemple. comment fonctionnent les commandes UNIX watch ou top.

Je pense que dans votre cas, vous imprimez "Invite>" mais lorsque vous obtenez un "Hello World", remplacez "Invite>" par "Bonjour tout le monde", puis imprimez "Invite>" sur la ligne ci-dessous. Je ne pense pas que vous pouvez le faire avec l'impression de sortie régulière au terminal.

Vous pouvez faire ce que vous voulez en utilisant la bibliothèque curses de Python. Je ne l'ai jamais utilisé donc je ne peux pas vous dire comment résoudre votre problème (ou si le module sera même capable de résoudre votre problème), mais je pense qu'il vaut la peine d'y jeter un coup d'œil. Une recherche de "tutoriel Python curses" fourni un PDF tutorial document qui semble utile.

+0

Je préférerais ne pas avoir de dépendances supplémentaires. Mais puisque ce n'est qu'un changement cosmétique. Je pense que je vais jeter un coup d'œil aux sorts et revenir au comportement actuel s'il n'existe pas :) – Jim

1

Vous devez mettre à jour stdout à partir d'un seul thread, pas à partir de plusieurs threads ... ou bien vous n'avez aucun contrôle sur les E/S entrelacées.

Vous voudrez créer un seul thread pour l'écriture de sortie.

Vous pouvez utiliser une file d'attente dans le thread et demander à tous les autres threads d'écrire leurs informations de journalisation de sortie .. puis lire cette file d'attente et écrire sur stdout à des moments appropriés avec votre message d'invite.

+1

Les sorties provenant des autres threads sont gérées de manière cohérente et ne sont pas entrelacées entre elles. C'est l'entrée qui est le problème. En utilisant une file d'attente, et un seul thread pour l'entrée et la sortie, je pense que je devrais implémenter ma propre gestion des entrées. raw_input bloque actuellement le thread actif jusqu'à EOL. – Jim

Questions connexes