it-swarm.com.de

Warum druckt Python Unicode-Zeichen, wenn die Standardcodierung ASCII ist?

Aus der Python 2.6-Shell:

>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>> 

Ich habe erwartet, dass nach der print-Anweisung entweder ein Kauderwelsch oder ein Fehler auftritt, da das Zeichen "é" nicht Teil von ASCII ist und ich keine Kodierung angegeben habe. Ich verstehe nicht, was ASCII als Standardcodierung bedeutet.

EDIT

Ich habe den Schnitt in den Bereich Answers verschoben und akzeptiere ihn wie vorgeschlagen.

132
Michael Ekoka

Ich denke, wir können dank der Einzelteile aus verschiedenen Antworten eine Erklärung zusammenfassen.

Indem Sie versuchen, eine Unicode-Zeichenfolge zu drucken, versuchen u '\ xe9', Python implizit, diese Zeichenfolge mit dem derzeit in sys.stdout.encoding gespeicherten Codierschema zu codieren. Python übernimmt diese Einstellung tatsächlich aus der Umgebung, aus der sie initiiert wurde. Wenn in der Umgebung keine ordnungsgemäße Codierung gefunden wird, wird nur dann auf Standard, ASCII zurückgesetzt.

Zum Beispiel verwende ich eine Bash-Shell, die standardmäßig UTF-8 codiert. Wenn ich Python von dort starte, wird diese Einstellung übernommen und verwendet:

$ python

>>> import sys
>>> print sys.stdout.encoding
UTF-8

Verlassen wir für einen Moment die Python Shell und setzen die Umgebung von bash mit einer falschen Codierung:

$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.

Starten Sie dann die python -Shell erneut und stellen Sie sicher, dass tatsächlich die Standard-ASCII-Codierung wiederhergestellt wird.

$ python

>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968

Bingo!

Wenn Sie jetzt versuchen, ein Unicode-Zeichen außerhalb von ASCII auszugeben, sollte eine Nice-Fehlermeldung angezeigt werden

>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 0: ordinal not in range(128)

Verlassen wir Python und verwerfen die Bash-Shell.

Wir werden nun beobachten, was passiert, nachdem Python Zeichenfolgen ausgegeben hat. Dazu starten wir zuerst eine Bash-Shell in einem Grafikterminal (ich verwende Gnome-Terminal) und stellen das Terminal so ein, dass die Ausgabe mit ISO-8859-1 (auch bekannt als Latin-1) dekodiert wird (Grafikterminals haben normalerweise die Option: Set Character Encoding in einem ihrer Dropdown-Menüs). Beachten Sie, dass dies nicht die eigentliche Shell-Umgebung Kodierung ändert, sondern nur die Art und Weise, wie das Terminal selbst die Ausgabe dekodiert, die es gibt, ein bisschen wie es ein Webbrowser tut. Sie können daher die Codierung des Terminals unabhängig von der Shell-Umgebung ändern. Dann starten wir Python von der Shell aus und stellen sicher, dass sys.stdout.encoding auf die Codierung der Shell-Umgebung eingestellt ist (UTF-8 für mich):

$ python

>>> import sys

>>> print sys.stdout.encoding
UTF-8

>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>

(1) python gibt die Binärzeichenfolge unverändert aus, das Terminal empfängt sie und versucht, ihren Wert mit der Latin-1-Zeichentabelle abzugleichen. In latin-1 ergibt 0xe9 oder 233 das Zeichen "é", und das ist es, was das Terminal anzeigt.

(2) python versucht, implizit ​​die Unicode-Zeichenfolge mit dem aktuell in sys.stdout.encoding festgelegten Schema zu codieren, in diesem Fall mit "UTF-8". Nach der UTF-8-Codierung lautet die resultierende Binärzeichenfolge '\ xc3\xa9' (siehe spätere Erläuterung). Das Terminal empfängt den Stream als solchen und versucht, 0xc3a9 mit latin-1 zu dekodieren, aber latin-1 geht von 0 auf 255 und dekodiert daher jeweils nur 1 Byte Stream. 0xc3a9 ist 2 Bytes lang, Latin-1-Decoder interpretiert es daher als 0xc3 (195) und 0xa9 (169) und ergibt 2 Zeichen: Ã und ©.

(3) python codiert den Unicode-Codepunkt u '\ xe9' (233) mit dem Latin-1-Schema. Es stellt sich heraus, dass der Bereich der Latin-1-Codepunkte 0-255 beträgt und genau dasselbe Zeichen wie Unicode innerhalb dieses Bereichs angibt. Daher ergeben Unicode-Codepunkte in diesem Bereich denselben Wert, wenn sie in Latin-1 codiert werden. Also ergibt u '\ xe9' (233), das in lateinisch-1 codiert ist, auch die Binärzeichenfolge '\ xe9'. Terminal empfängt diesen Wert und versucht, ihn auf der Latin-1-Zeichentabelle abzugleichen. Genau wie bei case (1) ergibt es "é" und genau das wird angezeigt.

Lassen Sie uns nun die Codierungseinstellungen des Terminals im Dropdown-Menü in UTF-8 ändern (so, als würden Sie die Codierungseinstellungen Ihres Webbrowsers ändern). Es ist nicht erforderlich, Python anzuhalten oder die Shell neu zu starten. Die Kodierung des Terminals stimmt jetzt mit der von Python überein. Versuchen wir es noch einmal:

>>> print '\xe9' # (4)

>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)

>>>

(4) python gibt einen binären String aus, wie er ist. Das Terminal versucht, diesen Stream mit UTF-8 zu dekodieren. UTF-8 versteht den Wert 0xe9 jedoch nicht (siehe spätere Erläuterung) und kann ihn daher nicht in einen Unicode-Codepunkt konvertieren. Kein Codepunkt gefunden, kein Zeichen gedruckt.

(5) python versucht, implizit ​​die Unicode-Zeichenfolge mit allem zu codieren, was in sys.stdout.encoding enthalten ist. Immer noch "UTF-8". Die resultierende Binärzeichenfolge lautet '\ xc3\xa9'. Das Terminal empfängt den Stream und versucht, 0xc3a9 auch mit UTF-8 zu dekodieren. Sie liefert den Rückgabewert 0xe9 (233), der in der Unicode-Zeichentabelle auf das Symbol "é" zeigt. Das Terminal zeigt "é" an.

(6) python codiert einen Unicode-String mit Latin-1 und liefert einen Binär-String mit demselben Wert '\ xe9'. Auch für das Terminal ist dies so ziemlich dasselbe wie für case (4).

Schlussfolgerungen: - Python gibt Nicht-Unicode-Zeichenfolgen als Rohdaten aus, ohne die Standardcodierung zu berücksichtigen. Das Terminal zeigt sie nur dann an, wenn ihre aktuelle Codierung mit den Daten übereinstimmt. - Python gibt Unicode-Zeichenfolgen aus, nachdem sie mit dem in sys.stdout.encoding angegebenen Schema codiert wurden. - Python ruft diese Einstellung von der Shell-Umgebung ab. - Das Terminal zeigt die Ausgabe gemäß den eigenen Kodierungseinstellungen an. - Die Codierung des Terminals ist unabhängig von der der Shell.


Weitere Details zu Unicode, UTF-8 und Latin-1:

Unicode ist im Grunde eine Zeichentabelle, in der einige Schlüssel (Codepunkte) herkömmlicherweise zugewiesen wurden, um auf einige Symbole zu verweisen. z.B. Konventionell wurde entschieden, dass der Schlüssel 0xe9 (233) der Wert ist, der auf das Symbol 'é' zeigt. ASCII und Unicode verwenden dieselben Codepunkte von 0 bis 127 wie Latin-1 und Unicode von 0 bis 255. Das heißt, 0x41 zeigt auf 'A' in ASCII, Latin-1 und Unicode, 0xc8 zeigt auf 'Ü' in Latin-1 und Unicode, 0xe9 zeigt auf 'é' in Latin-1 und Unicode.

Bei der Arbeit mit elektronischen Geräten müssen Unicode-Codepunkte auf effiziente Weise elektronisch dargestellt werden. Darum geht es bei Codierungsschemata. Es gibt verschiedene Unicode-Codierungsschemata (utf7, UTF-8, UTF-16, UTF-32). Die intuitivste und einfachste Codierungsmethode besteht darin, den Wert eines Codepunkts in der Unicode-Map als Wert für seine elektronische Form zu verwenden. Derzeit verfügt Unicode jedoch über mehr als eine Million Codepunkte, was bedeutet, dass einige von ihnen 3 Byte benötigen ausgedrückt. Um effizient mit Text arbeiten zu können, wäre eine 1: 1-Zuordnung eher unpraktisch, da alle Codepunkte unabhängig von ihrem tatsächlichen Bedarf auf genau dem gleichen Platz mit mindestens 3 Bytes pro Zeichen gespeichert werden müssten.

Die meisten Codierungsschemata weisen hinsichtlich des Platzbedarfs Mängel auf, die wirtschaftlichsten decken nicht alle Unicode-Codepunkte ab, zum Beispiel deckt ASCII nur die ersten 128 ab, während Latin-1 die ersten 256 abdeckt. Andere, die versuchen, umfassender zu sein, enden ebenfalls Verschwendung, da sie mehr Bytes als nötig benötigen, auch für gewöhnliche "billige" Zeichen. UTF-16 verwendet beispielsweise mindestens 2 Byte pro Zeichen, einschließlich der im ASCII-Bereich ('B' (65) erfordert immer noch 2 Byte Speicher in UTF-16). UTF-32 ist noch verschwenderischer, da alle Zeichen in 4 Bytes gespeichert werden.

UTF-8 hat das Dilemma mit einem Schema gelöst, das Codepunkte mit einer variablen Anzahl von Byte-Leerzeichen speichern kann. Als Teil seiner Codierungsstrategie fügt UTF-8 Codepunkte mit Flag-Bits ein, die (vermutlich für Decoder) ihren Platzbedarf und ihre Grenzen angeben.

TF-8-Codierung von Unicode-Codepunkten im ASCII-Bereich (0-127):

0xxx xxxx  (in binary)
  • die x zeigen den tatsächlichen Platz an, der reserviert ist, um den Codepunkt während des Codierens zu "speichern"
  • Die führende 0 ist ein Flag, das dem UTF-8-Decoder anzeigt, dass dieser Codepunkt nur 1 Byte benötigt.
  • nach der Codierung ändert UTF-8 den Wert der Codepunkte in diesem bestimmten Bereich nicht (d. h. 65, die in UTF-8 codiert sind, sind ebenfalls 65). In Anbetracht der Tatsache, dass Unicode und ASCII im selben Bereich auch kompatibel sind, werden UTF-8 und ASCII auch in diesem Bereich kompatibel.

z.B. Der Unicode-Codepunkt für 'B' ist '0x42' oder 0100 0010 in binär (wie gesagt, es ist das gleiche in ASCII). Nach der Kodierung in UTF-8 wird es:

0xxx xxxx  <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010  <-- Unicode code point 0x42
0100 0010  <-- UTF-8 encoded (exactly the same)

TF-8-Codierung von Unicode-Codepunkten über 127 (nicht ASCII):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • die führenden Bits "110" zeigen dem UTF-8-Decoder den Beginn eines Codepunkts an, der in 2 Bytes codiert ist, während "1110" 3 Bytes anzeigt, während "11110" 4 Bytes usw. anzeigt.
  • die inneren '10'-Flag-Bits werden verwendet, um den Beginn eines inneren Bytes zu signalisieren.
  • wieder markieren die x den Raum, in dem der Unicode-Codepunktwert nach der Codierung gespeichert wird.

z.B. Der Unicode-Codepunkt 'é' ist 0xe9 (233).

1110 1001    <-- 0xe9

Wenn UTF-8 diesen Wert codiert, stellt es fest, dass der Wert größer als 127 und kleiner als 2048 ist. Daher sollte er in 2 Bytes codiert werden:

110x xxxx 10xx xxxx   <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001   <-- 0xe9
1100 0011 1010 1001   <-- 'é' after UTF-8 encoding
C    3    A    9

Der 0xe9-Unicode-Code zeigt nach der UTF-8-Codierung auf 0xc3a9. Welches ist genau, wie das Terminal es empfängt. Wenn Ihr Terminal so eingestellt ist, dass Zeichenfolgen mit Latin-1 (einer der nicht-Unicode-Legacy-Codierungen) decodiert werden, wird "" angezeigt, da 0xc3 in Latin-1 einfach auf "" und 0xa9 auf "" verweist.

101
Michael Ekoka

Wenn Unicode-Zeichen in stdout gedruckt werden, wird sys.stdout.encoding verwendet. Es wird angenommen, dass ein Nicht-Unicode-Zeichen in sys.stdout.encoding ist und nur an das Terminal gesendet wird. Auf meinem System (Python 2):

>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9'  # Byte is just sent to terminal and assumed to be CP437.
Θ

sys.getdefaultencoding() wird nur verwendet, wenn Python keine andere Option hat.

Beachten Sie, dass Python 3.6 oder höher Kodierungen unter Windows ignoriert und Unicode-APIs verwendet, um Unicode in das Terminal zu schreiben. Es werden keine UnicodeEncodeError-Warnungen angezeigt, und das richtige Zeichen wird angezeigt, wenn die Schriftart dies unterstützt. Selbst wenn die Schriftart nicht unterstützt, können die Zeichen immer noch vom Terminal aus in eine Anwendung mit einer unterstützenden Schriftart eingefügt werden. Aktualisierung!

25
Mark Tolonen

Der Python REPL versucht, die zu verwendende Kodierung aus Ihrer Umgebung herauszufinden. Wenn es etwas Vernünftiges findet, dann funktioniert alles einfach. Es ist, wenn es nicht herausfinden kann, was los ist, dass es ausfällt.

>>> print sys.stdout.encoding
UTF-8

Sie haben eine Codierung angegeben, indem Sie eine explizite Unicode-Zeichenfolge eingeben. Vergleichen Sie die Ergebnisse, wenn Sie das Präfix u nicht verwenden.

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'

>>> 

Im Fall von \xe9 übernimmt Python Ihre Standardkodierung (Ascii) und druckt ... etwas Leeres.

4
Mark Rushakoff

Für mich geht das:

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')
0
user3611630

Gemäß Python-Standard/implizite Zeichenfolgencodierungen und -konvertierungen :

  • Wenn printing unicode ist, ist es encoded mit <file>.encoding.
    • wenn encoding nicht gesetzt ist, wird unicode implizit in str konvertiert (da der Codec sys.getdefaultencoding() lautet, d. h. ascii, würden alle nationalen Zeichen UnicodeEncodeError verursachen.)
    • für Standard-Streams wird encoding aus der Umgebung abgeleitet. Er wird normalerweise für tty-Streams (aus den Ländereinstellungen des Terminals) festgelegt, wird jedoch wahrscheinlich nicht für Pipes festgelegt
      • ein print u'\xe9' wird wahrscheinlich erfolgreich sein, wenn die Ausgabe an ein Terminal gesendet wird, und schlägt fehl, wenn er umgeleitet wird. Eine Lösung ist, encode() die Zeichenfolge mit der gewünschten Kodierung vor printing.
  • Bei printing str werden die Bytes unverändert zum Stream gesendet. Welche Glyphen das Terminal anzeigt, hängt von den Ländereinstellungen ab.
0
ivan_pozdeev