2012-10-10 3 views
6

Wenn ich Sysadmin-Skripte in Python schreibe, ist der Puffer auf sys.stdout, die jeden Aufruf von print() wirkt nervend, weil ich nicht Ich möchte nicht warten, bis ein Puffer geleert wird, und dann einen großen Teil der Zeilen gleichzeitig auf dem Bildschirm anzeigen. Stattdessen möchte ich einzelne Zeilen ausgeben, sobald eine neue Ausgabe vom Skript generiert wird. Ich möchte nicht einmal auf Zeilenumbrüche warten, um die Ausgabe zu sehen.Python-Standard-Idiom zum Setzen von sys.stdout Puffer auf Null funktioniert nicht mit Unicode

Ein oft Idiom verwendet, um dieses in Python zu tun ist,

import os 
import sys 
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) 

Das ist für mich für eine lange Zeit gut funktioniert. Jetzt ist mir aufgefallen, dass es mit Unicode nicht funktioniert. Bitte sehen Sie das folgende Skript:

#!/usr/bin/python 
# -*- coding: utf-8 -*- 

from __future__ import print_function, unicode_literals 

import os 
import sys 

print('Original encoding: {}'.format(sys.stdout.encoding)) 
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) 
print('New encoding: {}'.format(sys.stdout.encoding)) 

text = b'Eisb\xe4r' 
print(type(text)) 
print(text) 

text = text.decode('latin-1') 
print(type(text)) 
print(text) 

Dies ist auf die folgende Ausgabe führt:

Original encoding: UTF-8 
New encoding: None 
<type 'str'> 
Eisb▒r 
<type 'unicode'> 
Traceback (most recent call last): 
    File "./export_debug.py", line 18, in <module> 
    print(text) 
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 4: ordinal not in range(128) 

Es mir Stunden in Anspruch nahm den Grund dafür aufzuspüren (meine Original-Skript war viel länger als dieser minimale Debug-Skript). Es ist die Linie

sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) 

, die ich seit Jahren so kein Problem mit ihm erwartete. Kommentieren Sie einfach diese Zeile und die korrekte Ausgabe sollte wie folgt aussehen:

Original encoding: UTF-8 
New encoding: UTF-8 
<type 'str'> 
Eisb▒r 
<type 'unicode'> 
Eisbär 

Also was ist das Skript zu tun? Zur Vorbereitung meines Python 2.7-Code so nahe wie möglich zu Python 3.x, ich bin immer mit

from __future__ import print_function, unicode_literals 

dem Python den neuen Druck verwenden macht() - Funktion aber noch wichtiger: es Python Speicher macht alle Strings als Unicode intern standardmäßig. Ich habe eine Menge von Latin-1/ISO-8859-1 codierten Daten, zum Beispiel

text = b'Eisb\xe4r' 

damit die beabsichtigte Art und Weise zu umgehen, muß ich es zuerst in Unicode entschlüsseln, das ist, was

text = text.decode('latin-1') 

ist für. Da die Standardcodierung auf meinem System UTF-8 ist, verschlüsselt Python die interne Unicode-Zeichenkette immer dann, wenn ich einen String drucke, in UTF-8. Aber zuerst muss es intern in perfektem Unicode sein.

Nun, das alles funktioniert im Allgemeinen gut, nur nicht mit einem Null-Byte-Ausgangspuffer so weit. Irgendwelche Ideen? Ich habe bemerkt, dass sys.stdout.encoding nach der Null-Pufferungszeile nicht gesetzt ist, aber ich weiß nicht, wie ich es wieder einstellen soll. Es ist ein schreibgeschütztes Attribut und die OS-Umgebungsvariablen LC_ALL oder LC_CTYPE scheinen nur am Anfang des Python-Interpreters ausgewertet zu werden.

Bt .: Eisbär ist das deutsche Wort für Eisbär.

+0

@martineau Nun funktioniert der Vorschlag sys.stdout = codecs.getwriter ('utf8') (sys.stdout) auch nicht. Ich habe wirklich viel versucht und gesucht. Ich denke also, dass Ideen, ohne sie zu testen, nicht viel helfen. –

+0

Ich habe die Frage für Sie migriert. Das nächste Mal, einfach "Flagge" für die Aufmerksamkeit des Moderators und sagen Sie uns, was Sie brauchen! :) – slhck

+0

@MartenLehmann: Die Tatsache, dass es nicht getestet wurde, ist, warum ich es als Kommentar eher eine Antwort postete. – martineau

Antwort

6

Die Druckfunktion verwendet ein spezielles Flag, wenn auf ein Datei-Objekt zu schreiben, wodurch die PyFile_WriteObject Funktion des Python C API die Ausgabecodierung zum Abrufen der Unicode-zu-Byte Konvertierung zu tun, und indem den stdout Strom ersetzt Sie verloren die Codierung. Leider kann man nicht explizit neu gesetzt:

encoding = sys.stdout.encoding 
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) 
sys.stdout.encoding = encoding # Raises a TypeError; readonly attribute 

Sie können auch nicht die io.open function stattdessen verwenden, da es nicht Pufferung deaktiviert sein erlaubt, wenn Sie die encoding Option nutzen zu können, wollen würden Sie benötigen.

Der richtige Weg, um die Druckfunktion bündig sofort haben, ist das flush=True Schlüsselwort zu verwenden: mit einer benutzerdefinierten Druckfunktion

print(something, flush=True) 

Wem das zu langweilig ist überall hinzuzufügen, zu betrachten:

def print(*args, **kw): 
    flush = kw.pop('flush', True) # Python 2.7 doesn't support the flush keyword.. 
    __builtins__.print(*args, **kw) 
    if flush: 
     sys.stdout.flush() 

Da Python 2.7 print() Funktion nicht tatsächlich unterstützt das Flush-Schlüsselwort noch (Botheration), können Sie simulieren dass Sie stattdessen in dieser benutzerdefinierten Version eine explizite Flush hinzufügen.

+0

Sie können drei erste Zeilen Ihrer benutzerdefinierten 'print()' -Funktion mit diesem ersetzen: 'flush = kw.pop ('flush', True)'. – Tadeck

+0

@Tadeck: Toller Vorschlag, hinzugefügt. –

Verwandte Themen