Ich führe dieses Snippet zweimal im Ubuntu-Terminal aus (Codierung auf utf-8 eingestellt), einmal mit ./test.py
und dann mit ./test.py >out.txt
:
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
Ohne Umleitung wird Müll gedruckt. Bei Umleitung erhalte ich einen UnicodeDecodeError. Kann jemand erklären, warum ich den Fehler nur im zweiten Fall erhalte, oder noch besser ausführlich erklären, was in beiden Fällen hinter dem Vorhang vor sich geht?
Antworten:
Der ganze Schlüssel zu solchen Codierung Problemen ist zu verstehen , dass es im Prinzip zwei verschiedenen Konzepte von „string“ : (1) Kette von Zeichen , und (2) string / Array von Bytes. Diese Unterscheidung wurde lange Zeit aufgrund der historischen Allgegenwart von Codierungen mit nicht mehr als 256 Zeichen (ASCII, Latin-1, Windows-1252, Mac OS Roman,…) weitgehend ignoriert: Diese Codierungen ordnen eine Reihe gemeinsamer Zeichen zu Zahlen zwischen 0 und 255 (dh Bytes); Der relativ begrenzte Austausch von Dateien vor dem Aufkommen des Webs machte diese Situation inkompatibler Codierungen erträglich, da die meisten Programme die Tatsache ignorieren konnten, dass es mehrere Codierungen gab, solange sie Text produzierten, der auf demselben Betriebssystem blieb: solche Programme würden es einfach tun Behandeln Sie Text als Bytes (durch die vom Betriebssystem verwendete Codierung). Die richtige, moderne Ansicht trennt diese beiden Zeichenfolgenkonzepte anhand der folgenden zwei Punkte ordnungsgemäß:
Zeichen haben meistens nichts mit Computern zu tun: Man kann sie auf eine Kreidetafel usw. zeichnen, wie zum Beispiel بايثون, 中 蟒 und 🐍. "Zeichen" für Maschinen enthalten auch "Zeichenanweisungen" wie z. B. Leerzeichen, Wagenrücklauf, Anweisungen zum Festlegen der Schreibrichtung (für Arabisch usw.), Akzente usw. Der Unicode- Standard enthält eine sehr große Zeichenliste . Es deckt die meisten bekannten Zeichen ab.
Auf der anderen Seite müssen Computer abstrakte Zeichen in irgendeiner Weise darstellen: Dazu verwenden sie Arrays von Bytes (einschließlich Zahlen zwischen 0 und 255), da ihr Speicher in Byte-Blöcken vorliegt. Der erforderliche Prozess, der Zeichen in Bytes konvertiert, wird als Codierung bezeichnet . Daher benötigt ein Computer eine Codierung, um Zeichen darzustellen. Jeder auf Ihrem Computer vorhandene Text wird codiert (bis er angezeigt wird), unabhängig davon, ob er an ein Terminal gesendet wird (das auf eine bestimmte Weise codierte Zeichen erwartet) oder in einer Datei gespeichert wird. Um angezeigt oder richtig "verstanden" zu werden (beispielsweise durch den Python-Interpreter), werden Byteströme in Zeichen dekodiert . Ein paar Kodierungen(UTF-8, UTF-16,…) werden von Unicode für seine Zeichenliste definiert (Unicode definiert somit sowohl eine Liste von Zeichen als auch Codierungen für diese Zeichen - es gibt immer noch Stellen, an denen der Ausdruck "Unicode-Codierung" als Möglichkeit, auf das allgegenwärtige UTF-8 zu verweisen, dies ist jedoch eine falsche Terminologie, da Unicode mehrere Codierungen bereitstellt .
Zusammenfassend muss gesagt werden, dass Computer Zeichen intern mit Bytes darstellen müssen , und zwar durch zwei Operationen:
Einige Codierungen können nicht alle Zeichen (z. B. ASCII) codieren, während (einige) Unicode-Codierungen das Codieren aller Unicode-Zeichen ermöglichen. Die Codierung ist auch nicht unbedingt eindeutig , da einige Zeichen entweder direkt oder als Kombination dargestellt werden können (z. B. eines Basiszeichens und von Akzenten).
Beachten Sie, dass das Newline- Konzept eine zusätzliche Komplikationsebene darstellt , da es durch verschiedene (Steuer-) Zeichen dargestellt werden kann, die vom Betriebssystem abhängen (dies ist der Grund für den universellen Lesemodus für Newline-Dateien in Python ).
Was ich oben als "Zeichen" bezeichnet habe, nennt Unicode ein "vom Benutzer wahrgenommenes Zeichen ". Ein einzelnes vom Benutzer wahrgenommenes Zeichen kann manchmal in Unicode dargestellt werden, indem Zeichenteile (Basiszeichen, Akzente usw.) kombiniert werden, die an verschiedenen Indizes in der Unicode-Liste gefunden werden und als " Codepunkte " bezeichnet werden. Diese Codepunkte können zu einer Form kombiniert werden ein "Graphemcluster". Unicode führt somit zu einem dritten Konzept von Zeichenfolgen, das aus einer Folge von Unicode-Codepunkten besteht, die zwischen Byte- und Zeichenfolgen liegen und näher an letzteren liegen. Ich werde sie " Unicode-Strings " nennen (wie in Python 2).
Während Python Zeichenfolgen von (vom Benutzer wahrgenommenen) Zeichen drucken kann , sind Python-Nicht-Byte-Zeichenfolgen im Wesentlichen Sequenzen von Unicode-Codepunkten , nicht von vom Benutzer wahrgenommenen Zeichen. Die Codepunktwerte werden in der Python-
\u
und\U
Unicode-Zeichenfolgensyntax verwendet. Sie sollten nicht mit der Codierung eines Zeichens verwechselt werden (und müssen keine Beziehung dazu haben: Unicode-Codepunkte können auf verschiedene Arten codiert werden).Dies hat eine wichtige Konsequenz: die Länge eines Python (Unicode) string ist die Anzahl der Codepunkte, die ist nicht immer die Zahl der Benutzer wahrgenommenen Zeichen : also
s = "\u1100\u1161\u11a8"; print(s, "len", len(s))
(Python 3) gibt각 len 3
trotzs
einer einzigen benutzer wahrgenommen (Korean) mit Zeichen (weil es mit 3 Codepunkten dargestellt wird - auch wenn es nicht muss, wieprint("\uac01")
zeigt). Unter vielen praktischen Umständen entspricht die Länge einer Zeichenfolge jedoch der Anzahl der vom Benutzer wahrgenommenen Zeichen, da viele Zeichen normalerweise von Python als einzelner Unicode-Codepunkt gespeichert werden.In Python 2 werden Unicode-Zeichenfolgen als "Unicode-Zeichenfolgen" (
unicode
Typ, Literalformu"…"
) bezeichnet, während Byte-Arrays "Zeichenfolgen" sind (str
Typ, bei dem das Array von Bytes beispielsweise mit Zeichenfolgenliteralen erstellt werden kann"…"
). In Python 3 werden Unicode-Zeichenfolgen einfach als "Zeichenfolgen" (str
Typ, Literalform"…"
) bezeichnet, während Byte-Arrays "Bytes" (bytes
Typ, Literalformb"…"
) sind. Infolgedessen"🐍"[0]
ergibt so etwas wie ein anderes Ergebnis in Python 2 ('\xf0'
ein Byte) und Python 3 ("🐍"
das erste und einzige Zeichen).Mit diesen wenigen wichtigen Punkten sollten Sie in der Lage sein, die meisten Fragen im Zusammenhang mit der Codierung zu verstehen!
Normalerweise sollten Sie beim Drucken
u"…"
auf einem Terminal keinen Müll bekommen: Python kennt die Codierung Ihres Terminals. Tatsächlich können Sie überprüfen, welche Codierung das Terminal erwartet:Wenn Ihre Eingabezeichen mit der Codierung des Terminals codiert werden können, wird Python dies tun und die entsprechenden Bytes an Ihr Terminal senden, ohne sich zu beschweren. Das Terminal bemüht sich dann, die Zeichen nach dem Decodieren der Eingabebytes anzuzeigen (im schlimmsten Fall enthält die Terminalschrift einige der Zeichen nicht und druckt stattdessen eine Art Leerzeichen).
Wenn Ihre Eingabezeichen nicht mit der Codierung des Terminals codiert werden können, bedeutet dies, dass das Terminal nicht für die Anzeige dieser Zeichen konfiguriert ist. Python wird sich beschweren (in Python mit einem,
UnicodeEncodeError
da die Zeichenfolge nicht in einer Weise codiert werden kann, die zu Ihrem Terminal passt). Die einzig mögliche Lösung besteht darin, ein Terminal zu verwenden, das die Zeichen anzeigen kann (entweder indem Sie das Terminal so konfigurieren, dass es eine Codierung akzeptiert, die Ihre Zeichen darstellen kann, oder indem Sie ein anderes Terminalprogramm verwenden). Dies ist wichtig, wenn Sie Programme verteilen, die in verschiedenen Umgebungen verwendet werden können: Nachrichten, die Sie drucken, sollten im Terminal des Benutzers darstellbar sein. Manchmal ist es daher am besten, sich an Zeichenfolgen zu halten, die nur ASCII-Zeichen enthalten.Wenn Sie jedoch die Ausgabe Ihres Programms umleiten oder weiterleiten , ist es im Allgemeinen nicht möglich, die Eingabecodierung des empfangenden Programms zu kennen, und der obige Code gibt eine Standardcodierung zurück: Keine (Python 2.7) oder UTF-8 ( Python 3):
Die Codierung von stdin, stdout und stderr kann jedoch werden gesetzt durch die
PYTHONIOENCODING
Umgebungsvariable, falls erforderlich:Wenn der Druck auf ein Terminal nicht das liefert, was Sie erwarten, können Sie überprüfen, ob die manuell eingegebene UTF-8-Codierung korrekt ist. Zum Beispiel ist Ihr erstes Zeichen (
\u001A
) nicht druckbar, wenn ich mich nicht irre .Unter http://wiki.python.org/moin/PrintFails finden Sie eine Lösung wie die folgende für Python 2.x:
Für Python 3 können Sie eine der zuvor in StackOverflow gestellten Fragen überprüfen .
quelle
Python codiert beim Schreiben in ein Terminal, eine Datei, eine Pipe usw. immer Unicode-Zeichenfolgen. Beim Schreiben in ein Terminal kann Python normalerweise die Codierung des Terminals bestimmen und korrekt verwenden. Beim Schreiben in eine Datei oder Pipe verwendet Python standardmäßig die ASCII-Codierung, sofern nicht ausdrücklich anders angegeben. Python kann gesagt werden, was zu tun ist, wenn die Ausgabe durch die
PYTHONIOENCODING
Umgebungsvariable geleitet wird. Eine Shell kann diese Variable festlegen, bevor die Python-Ausgabe in eine Datei oder Pipe umgeleitet wird, damit die richtige Codierung bekannt ist.In Ihrem Fall haben Sie 4 ungewöhnliche Zeichen gedruckt, die Ihr Terminal in seiner Schriftart nicht unterstützt hat. Hier sind einige Beispiele zur Erläuterung des Verhaltens mit Zeichen, die tatsächlich von meinem Terminal unterstützt werden (das cp437 und nicht UTF-8 verwendet).
Beispiel 1
Beachten Sie, dass der
#coding
Kommentar die Codierung angibt, in der die Quelldatei gespeichert ist. Ich habe utf8 gewählt, um Zeichen in der Quelle zu unterstützen, die mein Terminal nicht unterstützen konnte. Die Codierung wurde an stderr umgeleitet, sodass sie angezeigt wird, wenn sie an eine Datei umgeleitet wird.Ausgabe (direkt vom Terminal ausführen)
Python hat die Codierung des Terminals korrekt bestimmt.
Ausgabe (in Datei umgeleitet)
Python konnte die Codierung (Keine) nicht bestimmen, daher wurde die Standardeinstellung 'ascii' verwendet. ASCII unterstützt nur die Konvertierung der ersten 128 Zeichen von Unicode.
Ausgabe (in Datei umgeleitet, PYTHONIOENCODING = cp437)
und meine Ausgabedatei war korrekt:
Beispiel 2
Jetzt werde ich ein Zeichen in die Quelle einfügen, das von meinem Terminal nicht unterstützt wird:
Ausgabe (direkt vom Terminal ausführen)
Mein Terminal hat das letzte chinesische Schriftzeichen nicht verstanden.
Ausgabe (direkt ausführen, PYTHONIOENCODING = 437: ersetzen)
Mit der Codierung können Fehlerbehandlungsroutinen angegeben werden. In diesem Fall wurden unbekannte Zeichen durch ersetzt
?
.ignore
undxmlcharrefreplace
sind einige andere Optionen. Bei Verwendung von UTF8 (das das Codieren aller Unicode-Zeichen unterstützt) werden keine Ersetzungen vorgenommen, aber die zur Anzeige der Zeichen verwendete Schriftart muss diese weiterhin unterstützen.quelle
PYTHONIOENCODING
. Das zu tun,print string.encode("UTF-8")
was von @Ismail vorgeschlagen wurde, hat für mich funktioniert.chcp
Codepage sie nicht unterstützt. Um dies zu vermeidenUnicodeEncodeError: 'charmap'
, können Sie daswin-unicode-console
Paket installieren .PYTHONIOENCODING=utf-8
löst das Problem.Codieren Sie es beim Drucken
Dies liegt daran, dass wenn Sie das Skript manuell ausführen, Python es codiert, bevor es an das Terminal ausgegeben wird. Wenn Sie es weiterleiten, codiert Python es nicht selbst, sodass Sie es manuell ausführen müssen, wenn Sie E / A ausführen.
quelle
win-unicode-console
(Windows) oder akzeptieren Sie einen Befehlszeilenparameter (falls erforderlich).