2013-04-13 7 views
9

Ich brauche eine Funktion, die Eingabe in einen Puffer liest, wie raw_input() würde, aber statt Eingabe und Blockierung Echo bis zur Rückgabe einer vollen Zeile, sollte es Echo unterdrücken und rufen jedes Mal einen Rückruf auf, wenn sich der Puffer ändert.Python, "gefilterte" Zeile Bearbeitung, lesen stdin von Char ohne Echo

Ich sage "Puffer ändert" anstelle von "Zeichen wird gelesen", weil, wie raw_input(), ich möchte, dass es besondere Tasten zu erkennen. Die Rücktaste sollte zum Beispiel funktionieren.

Wenn ich wollte, zum Beispiel den Rückruf verwenden groß geschrieben Echo Eingang zu simulieren, würde der Code wie folgt aussehen:

def callback(text): 
    print '\r' + text.upper() 

read_input(callback) 

Wie kann ich das erreichen?

HINWEIS: Ich habe versucht, readline und curses zu verwenden, um meine Runden zu kommen, aber beide Python-Bindungen sind unvollständig. curses kann nicht gestartet werden, ohne den gesamten Bildschirm zu löschen, und readline bietet einen einzelnen Haken, bevor eine Eingabe beginnt.

Antwort

10

Nun, ich schrieb den Code von Hand. Ich werde eine Erklärung für zukünftige Referenz hinterlassen.

Requirements

import sys, tty, termios, codecs, unicodedata 
from contextlib import contextmanager 

Zeilenpuffer Deaktivieren

Das erste Problem, das entsteht, wenn lediglich das Lesen stdinLinie Pufferung ist. Wir möchten, dass einzelne Zeichen ohne eine erforderliche Zeilenschaltung unser Programm erreichen, und das ist nicht die Standardfunktion des Terminals.

Dazu schrieb ich einen Kontext-Manager, der tty Konfiguration Griffe:

@contextmanager 
def cbreak(): 
    old_attrs = termios.tcgetattr(sys.stdin) 
    tty.setcbreak(sys.stdin) 
    try: 
     yield 
    finally: 
     termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attrs) 

Dieser Manager ermöglicht das folgende Idiom:

with cbreak(): 
    single_char_no_newline = sys.stdin.read(1) 

Es ist wichtig, die Sanierung durchzuführen, wenn wir fertig sind oder das Terminal benötigt möglicherweise eine reset.

Dekodierungs stdin

Das zweite Problem mit nur das Lesen stdin wird kodiert. Nicht-ASCII-Unicode-Zeichen erreichen uns Byte für Byte, was völlig unerwünscht ist.

richtig stdin zu dekodieren, schrieb ich einen Generator an, die wir für Unicode-Zeichen laufen kann:

def uinput(): 
    reader = codecs.getreader(sys.stdin.encoding)(sys.stdin) 
    with cbreak(): 
     while True: 
      yield reader.read(1) 

Diese Überleitungen fehlschlagen. Ich bin mir nicht sicher. Für meinen Anwendungsfall nimmt es jedoch die richtige Kodierung auf und erzeugt einen Strom von Zeichen.

Umgang mit Sonderzeichen

Zunächst einmal sollten wir druckbare Zeichen außer Kontrolle diejenigen zu sagen in der Lage:

def is_printable(c): 
    return not unicodedata.category(c).startswith('C') 

Abgesehen von printables, denn jetzt, ich will nur behandeln ← Backspace und die CtrlD Sequenz:

def is_backspace(c): 
    return c in ('\x08','\x7F') 

def is_interrupt(c): 
    return c == '\x04' 

Zusammensetzen: xinput()

Jetzt ist alles vorhanden. Der ursprüngliche Vertrag für die Funktion, die ich wollte, war Eingabe lesen, Sonderzeichen behandeln, Callback aufrufen. Die Umsetzung spiegelt genau das:

def xinput(callback): 
    text = '' 

    for c in uinput(): 
     if is_printable(c): text += c 
     elif is_backspace(c): text = text[:-1] 
     elif is_interrupt(c): break 

     callback(text) 

    return text 

Probieren Sie es aus

def test(text): 
    print 'Buffer now holds', text 

xinput(test) 

es Laufen und die Eingabe Hellx← Backspaceo Welt zeigt:

Buffer now holds H 
Buffer now holds He 
Buffer now holds Hel 
Buffer now holds Hell 
Buffer now holds Hellx 
Buffer now holds Hell 
Buffer now holds Hello 
Buffer now holds Hello 
Buffer now holds Hello w 
Buffer now holds Hello wo 
Buffer now holds Hello wor 
Buffer now holds Hello worl 
Buffer now holds Hello world