Python Unicode-FAQ

Dieses Dokument erklärt, was es mit Unicode überhaupt auf sich hat, wie Unicode in der besten Programmiersprache der Welt realisiert ist, und wie es sich mit dem Unicode-Support in Win32 verhält.

Was ist ein String?

Wenn man von einer Sprache wie C/C++ kommt, oder von Assembler, oder von Pascal, oder von Java - dann sind Strings einfach Arrays von Char-Elementen.

Diese Annahme wird zum Beispiel auch durch das Win32-API gefördert - wenn man in Win32 von Unicode-Strings spricht (etwa davon, daß der NT-Kernel intern nur mit Unicode arbeitet) - so meint man, daß der Datentyp CHAR als short definiert ist. Ein CHAR* wird zu einem short* und so weiter.

Diese Annahme ist aber falsch. Oder besser gesagt, sie ist eine unzuläßige Einschränkung. Besser sagt man:

Ein String sei eine geordnete Menge von Zeichen, unabhängig von ihrer Darstellung im Computer.

In dieser Definition möchte ich folgende Punkte hervorheben:

  1. Wenn ich die Worte "Hallo, wie geht es dir?" ausspreche, dann spreche ich (gemäß dieser Definition) einen String aus. Beachte, daß ich diese Worte ausspreche, und ich kein Computer bin.
  2. Die Definition spricht mit voller Absicht nicht von "Array von Zeichen". Der Begriff "Array" impliziert, daß alle Elemente des Arrays gleich groß sind. Dies ist nicht notwendig der Fall.
  3. Die Definition spricht mit voller Absicht von Zeichen, nicht von "Chars". Der Begriff "Char" impliziert, daß es sich um einen elementaren Computerdatentyp handelt. Dies ist nicht der Fall, die Definition spricht von "menschlichen" Zeichen, unabhängig von ihrer Darstellung im Computer.

Aber, Strings sind doch Arrays von Char!

Jeder, der schon mal in einer der oben genannten Programmiersprachen gearbeitet hat, kann mir sofort leicht widersprechen. Zitat aus dem C-Header ...\include\winnt.h:

//
// ANSI (Multi-byte Character) types
//
typedef CHAR *PCHAR;
typedef CHAR *LPCH, *PCH;

oder, Zitat aus dem Java-Quellcode ...\src\share\classes\java\lang\String.java:

public final
class String implements java.io.Serializable, Comparable {
    /** The value is used for character storage. */
    private char value[];

Auch wenn in Java char ein Datentyp ist, der 65536 Werte annehmen kann (also ein "short", ein 16-Bit Wert, kein einfaches Byte) - es handelt sich doch immer noch offensichtlich um ein Array von Char, oder?

Diese Deklarationen sagen jedoch nur aus, daß in der jeweiligen Programmiersprache ein String auf diese Weise repräsentiert wird. Sie vermengen also den Begriff "String" mit "Darstellung des Strings im Computer".

Damit wir im folgenden nicht immer abstrakt in Kant'scher Manier über "Strings an sich" reden müssen, betrachten wir im folgenden den String:

S: "Die natürliche Sprache ist im Grunde frech improvisiert und in die Luft gebaut."

Das Zitat ist übrigens von Nietzsche.

Wie wurde S bisher im Computer repräsentiert?

In sehr vielen Programmen werden Strings einfach als Bytearrays abgespeichert. Wenn man beispielsweise einen Nicht-Unicodeeditor öffnet, und S (ohne die Anführungszeichen) abtippt, erhält man eine 79-Byte große Datei. Jedes Zeichen wurde also genau als ein Byte abgespeichert. Schauen wir uns diese Datei einmal in einem Hexeditor an:

$00000000 44696520 6E6174FC 726C6963 68652053 70726163  Die nat.rliche Sprac
$00000014 68652069 73742069 6D204772 756E6465 20667265  he ist im Grunde fre
$00000028 63682069 6D70726F 76697369 65727420 756E6420  ch improvisiert und 
$0000003C 696E2064 6965204C 75667420 67656261 75742E    in die Luft gebaut.

Betrachten wir das erste Zeichen D. Das Zeichen wird als Hexbyte 0x44 abgespeichert.

An dieser Stelle ist es wichtig zu verstehen, daß der Computer tatsächlich nicht D abspeichert, sondern 0x44. Der Computer hat keine Ahnung, daß das ein D sein soll, er kennt nur 0x44. Daß daraus in der Bildschirmdarstellung unser D wird, liegt an der Codetabelle.

Was macht eine Codetabelle?

Eine Codetabelle kann man Auffassen als eine Zuordnung Zeichen - Bildschirmdarstellung. Die bekannteste solche Zuordnung nennt sich mit vollem Namen ISO/IEC 646:1991 US ASCII, (oft einfach als ASCII oder 7-Bit ASCII geschrieben).

Wenn man sich diese ASCII-Tabelle anschaut, fällt folgendes auf:

  1. Nicht allen möglichen Bytes ist ein Zeichen zugeordnet. So ist etwa nicht definiert, wie das Byte 0xFC anzuzeigen ist.
  2. Nicht allen möglichen Zeichen ist ein Byte zugeordnet. So ist etwa nicht definiert, wie das Zeichen ü als Byte abgespeichert wird.

Es gibt eine Reihe von Codetabellen, die diese beiden Punkte berücksichtigen. Zum Beispiel gibt es die Microsoft Windows Codepage 1252 (ANSI), aus der man ablesen kann, daß in dieser Codetabelle das Byte 0xFC das Zeichen ü repräsentiert. Diese Codetabelle definiert für alle möglichen 256 Bytes ein dadurch repräsentiertes Zeichen.

In einer anderen Codetabelle gelten andere Regeln, beispielsweise wird in MS-DOS Codepage 850 (Multilingual Latin 1) das Zeichen ³ (hoch drei) als Byte 0xFC abgespeichert, während ü als das Byte 0x81 repräsentiert ist.

Also: Man sieht einem Byte nicht an, welches Zeichen es darstellen soll. Umgekehrt kann man natürlich auch sagen, daß man einem Zeichen nicht ansieht, als welches Byte es abgespeichert werden soll.

Wo ist das Problem?

  1. Es gibt Sprachen, die mehr als 256 Zeichen haben. Wenn man davon ausgeht, daß die Zuordnung Zeichen - Byterepräsentation eindeutig sein soll, gibt das ein Problem.
  2. Selbst, wenn man die Sonderzeichen der einen Sprache in einer Codetabelle unterbringen kann (beispielsweise unser im Deutschen gebräuchliches ü, das in der Windows Codepage 1252 (ANSI) abgebildet ist), so gibt es doch Zeichen, die in dieser Codetabelle nicht abgebildet werden, etwa altgriechische Zeichen - wie man sie in der Mathematik (und der Altphilologie :) benötigt.

Was ist Unicode?

Unicode ist erstmal nur eine Spezifikation, die jedem Zeichen auf der Welt eine eindeutige Nummer vergibt.

Sollten neue Zeichen entstehen - etwa, weil man einen bis dato unbekannten Eingeborenenstamm auf Papua-Neuguinea entdeckt - dann sorgt das Unicode-Konsortium dafür, daß es auch für deren Zeichen eigene, eindeutige Nummern gibt.

Wichtig: Diese Aussage sagt erstmal nichts über Bytes aus! Diese Aussage sagt auch gar nichts über Softwarerealisierung oder ähnliches aus - es geht einfach nur um eine eineindeutige Zuordnung Zeichen x - Zahl y.

Als Beispiel kann man sich die Unicode-Definition für die Sprache Tagalog anschauen. Mit großer Wahrscheinlichkeit wird ein deutschsprachiger Leser keines der Zeichen dieser Sprache kennen. Alle aber haben eine eindeutige Nummer!

Was sind Encodings?

Das Encoding ist eine Vorschrift, wie ich die Zahlen als Bytes repräsentiere. Erst mit dieser Vorschrift kann ich Unicode-Strings abspeichern. Die Zeichen können nicht per se abgespeichert werden, es muß festgelegt werden, wie diese Zeichen abgespeichert werden, und genau das ist das Encoding.

Beispielsweise gibt es eine Vorschrift mit dem Namen

UTF-16LE

Die praktisch folgendes besagt: jedes Zeichen wird als zwei Byte abgespeichert (16 Bit), und zwar im "Little Endian" Format (wie bei Intel-Prozessoren üblich). Beispielsweise wird die Zahl 60 als 0x3c00 abgebildet, die Zahl 24600 als 0x1860 usw.

Der Nachteil dieses Encodings ist, daß es exakt doppelt so viel Speicherplatz belegt, wie eine "klassische" Ein-Byte-Ist-Ein-Zeichen-Repräsentation mit Codetabellen.

Es gibt übrigens noch einen weiteren Nachteil: selbst dieser 16-Bit-Wertebereich reicht nicht aus, um alle Zeichen der Welt darzustellen, deshalb gibt es inzwischen Vorschriften, die jedes Zeichen als 32-Bit-Wert repräsentieren.

Ein Vorteil dieser Vorschrift ist allerdings, daß jedes Zeichen eine genau festgelegte Menge an Bytes benötigt (und umgekehrt). Man kann deshalb ad hoc wissen, daß unser netter Satz S in UTF-16LE genau doppelt soviele Bytes belegen wird, also 158 Bytes. Das ganze sieht als Hexdump dann so aus:

$00000000 44006900 65002000 6E006100 74008100 72006C00  D.i.e. .n.a.t...r.l.
$00000014 69006300 68006500 20005300 70007200 61006300  i.c.h.e. .S.p.r.a.c.
$00000028 68006500 20006900 73007400 20006900 6D002000  h.e. .i.s.t. .i.m. .
$0000003C 47007200 75006E00 64006500 20006600 72006500  G.r.u.n.d.e. .f.r.e.
$00000050 63006800 20006900 6D007000 72006F00 76006900  c.h. .i.m.p.r.o.v.i.
$00000064 73006900 65007200 74002000 75006E00 64002000  s.i.e.r.t. .u.n.d. .
$00000078 69006E00 20006400 69006500 20004C00 75006600  i.n. .d.i.e. .L.u.f.
$0000008C 74002000 67006500 62006100 75007400 2E00      t. .g.e.b.a.u.t...  

Es gibt auch eine Vorschrift

UTF-8

die für die "häufigsten" Zeichen als einzelne Bytes abbildet, wie in ASCII, und für die übrigen Zeichen eine Art "Escapesequenz" definiert. Der Nachteil ist, daß es nicht mehr jedes Zeichen eine genau festgelegte Menge an Bytes benötigt. Man kann also beispielsweise nur eine Abschätzung festlegen, wie viele Zeichen ein Benutzer in einem gegebenen Bytebereich abspeichern kann. Unser Satz S sieht in UTF-8 so aus:

$00000000 44696520 6E6174C2 81726C69 63686520 53707261  Die nat..rliche Spra
$00000014 63686520 69737420 696D2047 72756E64 65206672  che ist im Grunde fr
$00000028 65636820 696D7072 6F766973 69657274 20756E64  ech improvisiert und
$0000003C 20696E20 64696520 4C756674 20676562 6175742E   in die Luft gebaut.

Man sieht, daß das "Sonderzeichen" ü zu der Escapesequenz 0xC281 wird, während alle anderen Zeichen als genau ein Byte abgespeichert werden. Diese Repräsentation ist also 80 Bytes lang.

Eine weitere Vorschrift nennt sich

LATIN-1

Das ist eine Codepage - übrigens keine, die so direkt von Windows unterstützt wird -, und hat die Nachteile, die Codepages auch haben: es können nur exakt 256 Zeichen aus den tausenden Zeichen, die es überhaupt gibt. Alle anderen Zeichen können in LATIN-1 nicht abgebildet werden!

$00000000 44696520 6E617481 726C6963 68652053 70726163  Die nat.rliche Sprac
$00000014 68652069 73742069 6D204772 756E6465 20667265  he ist im Grunde fre
$00000028 63682069 6D70726F 76697369 65727420 756E6420  ch improvisiert und 
$0000003C 696E2064 6965204C 75667420 67656261 75742E    in die Luft gebaut. 

Mit unserem ü haben wir hier Glück gehabt, ein Tagalogianer hätte hier so seine Probleme.

TODO: Weitere Encodings vorstellen.

Unicode in Dateien

Folgendes Experiment für Windows-Benutzer:

Die Datei schaut wie folgt aus:

$00000000 FFFE4400 69006500 20006E00 61007400 FC007200  ..D.i.e. .n.a.t...r.
$00000014 6C006900 63006800 65002000 53007000 72006100  l.i.c.h.e. .S.p.r.a.
$00000028 63006800 65002000 69007300 74002000 69006D00  c.h.e. .i.s.t. .i.m.
$0000003C 20004700 72007500 6E006400 65002000 66007200   .G.r.u.n.d.e. .f.r.
$00000050 65006300 68002000 69006D00 70007200 6F007600  e.c.h. .i.m.p.r.o.v.
$00000064 69007300 69006500 72007400 20007500 6E006400  i.s.i.e.r.t. .u.n.d.
$00000078 20006900 6E002000 64006900 65002000 4C007500   .i.n. .d.i.e. .L.u.
$0000008C 66007400 20006700 65006200 61007500 74002E00  f.t. .g.e.b.a.u.t...

Ziemlich offensichtlich sieht man, daß der Text in UTF-16LE abgespeichert wurde. Irritierend sind aber die ersten beiden Bytes, 0xFFFE. Damit hat es folgendes auf sich:

Eine Datei ist ja nur eine Menge von Bytes. Damit man den Dateityp - hier Unicode in UTF-16LE - erkennen kann, wird eine Dateikennung verwendet, da sogenannte BOM (Byte Order Mark). Dieses legt das Format der Datei fest:

Damit kann jedes Unicode-fähige Programm beim Einlesen der Datei erkennen, welches Encoding benutzt wurde, und dementsprechend die Bytes in die richtigen Zeichen zurückwandeln.

Unicode in Python

Bevor wir einsteigen, gleich drei Warnungen für Windowsbenutzer.

  1. Pythonwin ist eine prima Python-IDE, die aber (zumindest in der aktuellen Version - build 150) Probleme mit Unicode-Zeichen hat, die direkt eingegeben werden (im Quelltext, oder im Interaktiven Fenster). Genauer, wenn man in der interaktiven Shell ein "ü" eingeben will, ist beim ersten mal " Tippen dieses Zeichen nicht sichtbar, aber gültig. Wenn man nochmal " tippt, damit man das Zeichen sieht, ist das ein Syntaxfehler.
  2. Das cygwin-Python hat auch so seine Eingabeprobleme. Genauer, wenn man ein ü tippt, passiert gar nichts.
  3. Leider hat auch die Win32-Console so seine Probleme mit Sonderzeichen. Genauer, die Console arbeitet auch auf einem deutschen Windows NT mit der Codepage 437, in der wie oben beschrieben die deutschen Umlaute anders dargestellt werden als etwa in Notepad (das mit der Windows-ANSI-Codepage arbeitet). (Das ist übrigens der Grund für folgendes: Wenn man im DevStudio ein C++ Programm schreibt, das Umlaute per printf ausgibt, sind die Umlaute in der Konsole nicht lesbar - weil sie in einer anderen Codepage dargestellt werden).

Näheres zu der haarigen Unicode-Unterstützung von Win32 gibt es in einem eigenen Kapitel, weiter unten. Um die folgenden Beispiele unter Windows durchführen, gibt es drei Möglichkeiten:

Normale Strings

Betrachten wir ein ganz einfaches nicht-Unicode Beispiel:

Python 2.2.2 (#37, Oct 14 2002, 17:02:34) [MSC 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> test = "Käsefondue"
>>> test
'K\x84sefondue'
>>> print test
Käsefondue
>>>

Daraus kann man übrigens ablesen, daß das ä als Byte 0x84 abgespeichert wird.

Unicode Strings

Jetzt das ganze in Unicode:

>>> test = u"Käsefondue"
>>> test
u'K\x84sefondue'
>>> print test

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeError: ASCII encoding error: ordinal not in range(128)
>>>

Oha, da geht was schief. Dazu folgende Anmerkungen:

Um den String auszugeben, gibt es die Methode .encode, die als Argument den Namen des Encodings erhält. Das obige Beispiel läßt sich so erweitern:

>>> print test.encode("utf-16le")
K &auml; s e f o n d u e
>>> print test.encode("utf-8")
K.&auml;sefondue
>>> print test.encode("latin-1")
K&auml;sefondue

Hm, Käsefondue, lecker!

Unicode-Strings in Dateien

Wie oben erwähnt, sollte einer Unicode-Datei ein BOM vorangestellt werden. Das ganze kann beispielsweise so erfolgen:

>>> import codecs
>>> codecs.BOM_LE
'\xff\xfe'
>>> f.close()
>>> f = open("test.txt","wb")
>>> f.write( codecs.BOM_LE )
>>> f.write( u'K\xe4sefondue'.encode("UTF-16LE"))
>>> f.close()

Damit wird eine wunderbare UTF-16LE-Codierte Textdatei mit dem Inhalt "Käsefondue" erstellt.

Das ganze kann man auch einfacher machen, wenn man einfach die "native" Endianess des Betriebssystems benutzen will:

>>> import codecs
>>> codecs.BOM_LE
'\xff\xfe'
>>> f.close()
>>> f = open("test.txt","wb",encoding="utf-16")
>>> f.write( u'K\xe4sefondue')
>>> f.close()

In diesem Fall wird das gute Python automatisch das richtige BOM erzeugen. Danke, Python!

Um die Datei einzulesen, kann man sich folgendes Codeschnippsel ansehen:

import codecs
def liesunicode(dateiname):
    datei = open(dateiname,"rb")
    bom = datei.read(2)
    if bom == codecs.BOM_LE:
        return datei.read().decode("utf-16le")

Wichtig, der BOM muß manuell ausgewertet werden. (Es gibt keine Vorschrift, die BOMs notwendig macht - es ist nur eine hilfreiche Konvention). Man kann sich das aber sparen, wenn man zumindest bereits weiß, daß es sich um eine UTF-16 Datei handelt - dann geht beispielsweise folgendes:

import codecs
f = codecs.open(dateiname,encoding="utf-16")
print f.readlines()
f.close()

Unicode in Win32

Windows unterstützt in den Versionen NT, 2000 und XP Unicode 2.0 in UTF-16. Zitat aus der MSDN: "Unicode is a worldwide character-encoding standard that uses 16-bit code values to represent all the characters used in modern computing".

Wie wir jetzt wissen, ist diese Aussage natürlich falsch - Unicode kann auch 8-Bit code für die Zeichenrepräsentation benutzen. Andererseits hat die Unicode-Unterstützung in Win32 - allein durch seine Marktmacht - vermutlich mehr für die Verbreitung des Standards getan als irgend ein anderes Programm.

Wenn man sagt, "Windows unterstützt Unicode", so ist damit nur gemeint, daß im NT-Kern alle Strings aus Zwei-Byte-Zeichen bestehen, und es entsprechende Wrapper von CHAR auf short gibt.

Wichtig: Jede Anwendung muß Programmaufwand betreiben, um Unicode zu unterstützen. Da ist gerade für C/C++-Programme natürlich einfacher, sich auf UTF-16LE zu beschränken, als alle möglichen Encodings zu unterstützen. Ein weiterer Grund für Python ;)