2010-01-17 12 views
11

Próbuję zezwolić użytkownikowi na wprowadzanie poleceń na konsoli przy użyciu metody raw_input(), to działa poprawnie. Problem polega na tym, że mam wątki w tle, które sporadycznie wyprowadzają informacje dziennika na ekran, a kiedy to robią, zepsuły monit wejściowy (ponieważ dane wyjściowe są przesyłane tam, gdzie aktualnie znajduje się kursor).Odczytanie danych wejściowych z trybu raw_input() bez konieczności zastąpienia przez inne wątki w Pythonie

To jest mały program w języku Python, który ilustruje, co mam na myśli.

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

To jest przykład co to może wyglądać, gdy uruchomię go:

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 

Co chcę jest dla zachęty, aby przejść wraz z wyjściem z wątku. Podobnie jak:

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> 

Jakieś pomysły, jak to osiągnąć bez uciekania się do brzydkich hacków? :)

Odpowiedz

23

Niedawno napotkałem ten problem i chciałbym pozostawić to rozwiązanie do wykorzystania w przyszłości. Rozwiązania te usuwają oczekujący tekst raw_input (readline) z terminala, drukują nowy tekst, a następnie ponownie drukują do terminala, który był w buforze raw_input.

Ten pierwszy program jest dość prosty, ale działa tylko poprawnie, gdy istnieje tylko 1 wiersz tekstu czeka na 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('> ') 

wyjściowa:

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

Drugi poprawnie obsługuje 2 lub więcej linie buforowane, ale ma więcej (standardowych) zależności modułów i wymaga odrobiny hakerów terminalowych:

#!/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('> ') 

Wyjście. Poprzednia linie readline rozliczone odpowiednio:

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

użytecznych źródeł:

How to get Linux console window width in Python

apt like column output - python library (Ten przykładowy kod pokazuje, jak uzyskać szerokość terminala dla obu Unix lub Windows)

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

+0

To jest dokładnie to, czego szukałem. Dzięki :) – Jim

+1

[Moduł "błogosławieństwo"] (https://pypi.python.org/pypi/blessings/) pozwala sformatować wyjście i poruszać się bez sięgania zbyt głęboko do wnętrzności terminala. – jfs

+0

Uwaga, w niektórych wersjach Pythona to się psuje, jeśli terminal jest zmieniany w czasie wykonywania z powodu błędu w module 'readline', który powoduje, że ignoruje on zdarzenia zmiany rozmiaru terminala (więc nie zmienia odpowiednio rozmiaru wewnętrznego bufora, co powoduje uszkodzenie kolumny logika liczenia). Zobacz https://bugs.python.org/issue23735. Na szczęście wygląda to na poprawione w Pythonie 3.5. Odpowiedź kick-ass inaczej :) – Thomas

0

Nie sądzę, że to możliwe. Jak to się powinno zachowywać? Nic nie wyświetla się, dopóki użytkownik nie naciśnie klawisza Enter? Jeśli tak jest, dane wyjściowe pojawiałyby się tylko wtedy, gdy użytkownik wyda polecenie (lub cokolwiek innego, czego oczekuje system), a to nie jest pożądane.

Wymyśla swoje wątki na innym pliku.

+0

Nie, chcę, aby komunikaty pojawiały się przed naciśnięciem przycisku. Ale podpowiedź przesunąłaby się w dół, aby wiadomości nie zostały nadpisane. – Jim

3

Myślę, że potrzebujesz czegoś, co pozwala na dynamiczne drukowanie/usuwanie/zastępowanie tekstu z okna terminala, np. jak działają polecenia UNIX watch lub top.

Myślę, że w twoim przypadku wydrukowałbyś "Prompt", ale wtedy, gdy otrzymasz "Hello World", zastąpisz "Prompt" "przez" Hello World ", a następnie wydrukujesz" Prompt> "na linii poniżej. Nie sądzę, że możesz to zrobić przy regularnym drukowaniu wydruków do terminalu.

Możesz być w stanie zrobić, co chcesz, używając biblioteki Pythona curses. Nigdy go nie używałem, więc nie mogę ci powiedzieć, jak rozwiązać twój problem (lub czy moduł będzie w stanie rozwiązać twój problem), ale myślę, że warto się przyjrzeć. Poszukiwanie "poradnika po python curses" dostarczyło PDF tutorial document, co wydaje się pomocne.

+0

Wolałbym nie mieć dodatkowych zależności. Ale ponieważ jest to tylko kosmetyczna zmiana. Myślę, że przyjrzę się klątwom i po prostu wrócę do obecnego zachowania, jeśli nie istnieje :) – Jim

1

Należy zaktualizować stdout z jednego wątku, a nie z wielu wątków ... lub nie masz kontroli nad przeplatanymi wejściami/wyjściami.

Będziesz chciał utworzyć pojedynczy wątek do zapisu wyjściowego.

można użyć kolejki w wątku i wszystkie pozostałe wątki zapisują na niej swoje dane wyjściowe do rejestrowania .. następnie odczytać z tej kolejki i napisać do stdout we właściwym czasie wraz z monitem.

+1

Dane wyjściowe pochodzące z innych wątków są przetwarzane w spójny sposób, nie są przeplatane ze sobą. To jest wejście, które jest problemem. Używając kolejki i pojedynczego wątku dla wejścia i wyjścia myślę, że musiałbym wdrożyć moją własną obsługę wejścia. raw_input obecnie blokuje aktywny wątek, aż do EOL. – Jim