[Python-de] Unicodefragen klarer gestellt

Martin v. Loewis martin at v.loewis.de
Thu Nov 7 21:46:34 EST 2002


"Gerson Kurz" <gerson.kurz at pergamon-software.de> writes:

> Nach ein bischen Analyse habe ich folgendes gesehen. Die von COM gelieferten
> Unicodestrings sehen beispielsweise so aus:
> 
> Dump of 26 Bytes for attrib 'name' of <type 'unicode'>
> $00000000 4F72646E 65722022 47656CF6 73636874 65204F62  Ordner "Gel.schte Ob
> $00000014 6A656B74 6522                                 jekte"

Wer hat diesen Dump produziert (also: welches Tool)?

> Frage 1) <frage>Wo ist das Encoding dieses Strings festgelegt?</frage> Das
> 'F6' entspricht dem 'ö' in der deutschen Codepage, ich glaube aber ziemlich
> sicher zu wissen, daß dem in z.B. Frankreich 'F6' nicht als 'ö' angezeigt
> wird. Adieu, schöne Unicodeweltvorteile?

Antwort auf die Frage: Ist aus dem Dump nicht ersichtlich, es muss also
irgendwo anders festgelegt sein - oder aber (wahrscheinlicher) das Tool,
welches den Dump produziert hat, hat Dich angelogen, und der tatsächliche
Speicherinhalt ist

4f 00 72 00 ...

In diesem Fall wäre das Encoding implizit in der Information, dass es
sich um <type 'unicode'> handelt - das Encoding ist halt "Unicode".

Unabhängig davon: In Frankreich bedeutet F6 in der lokalen codepage
ebenfalls 'ö', weil auch die französischen Windowsversionen 1252 als
Codepage verwenden. Du müsstest schon Russland oder so als Beispiel
nehmen.

> Ursprünglich hing ich der Illusion an:
> 
> 	Unicode = Jedes Zeichen wird als zwei Bytes abgespeichert.

Das ist ein Irrtum. Richtig ist

Unicode = Jedes Zeichen wird als eine Zahl abgespeichert.

Die nächste Frage ist dann: Wie speichert man eine Zahl ab. Da gibt es
mehrere Möglichkeiten: 2 Bytes oder 4 Bytes, little-endian oder
big-endian. Diese Frage sollte Dich aber nicht interessieren, wenn es
um die "interne" Repräsentation geht, weil Du mit der überhaupt nichts
zu tun hast.

Interessanter ist die Frage, wie die Zahlen extern abgespeichert
werden, also in Dateien usw. Da gibt es noch mehr Möglichkeiten.


> Dump of 26 Bytes for attrib 'name' of <type 'unicode'>
> $00000000 4F72646E 65722022 47656CF6 73636874 65204F62  Ordner "Gel.schte Ob
> $00000014 6A656B74 6522                                 jekte"
> 
> anschaue, so ist es ja zumindest an den Bytes nicht *inherent
> offensichtlich*, daß es sich um Unicode handeln soll

Deshalb glaube ich, dass das Tool lügt.

> (Um Fragen ob der Qualität des Hexdumps vorzubeugen, habe ich ein
> entsprechendes Codeschnipsel unten angehängt)

Ah, Marke Eigenbau. Das kann nicht funktionieren. Es gibt praktisch
*keine* Möglichkeit, mit Python-Code zu ermitteln, wie die interne
Reräsenation eines Unicode-Objekts ist.

Du schreibst

         byte = ord(data[i])

Das ist schon falsch: Der Wert, der von ord zurückgegeben wird, kann
auch größer als 256 werden. Ein Unicodestring besteht aus Zahlen,
nicht aus Bytes.

> Frage 2) <frage>Der Datentyp wechselt von "unicode" auf "str", wenn man
> ..encode aufruft. Warum?</frage>

.encode wandelt von der internen in eine externe Repräsentation
um. Eine externe Repräsentation ist eine Folge von Bytes. Der
Python-Datentyp für Folgen von Bytes ist str (str wird auch oft für
Zeichenfolgen verwendet, das geht allerdings nur, wenn man die
Kodierung der Zeichen kennt).

> >>> type(test.encode("utf-16le"))
> <type 'str'>

Es ist relativ unüblich, "utf-16le" zu verwenden. Die meisten Leute
auf Deinem Betriebssystem verwenden "cp1252". Es gibt *keinen*
essentiellen Unterschied zwischen "utf-16le" und "cp1252", aber
mehrere kleinere Unterschiede:
- In cp1252 wird jedes Zeichen mit einem Byte kodiert, in UTF-16
  mit 2.
- utf-16 kann alle Zeichen aus Unicode kodieren, cp1252 nur
  ein zweihundertsechsundfünfzigstel.

> Das ist mir so aufgefallen: ich hatte, zum Zwecke besseren
> Verständnisses meiner "warum nochmal encode, wenn schon
> unicode"-Frage, folgendes eingebaut:

Ganz einfach: Unicode-Strings sind Zahlenfolgen, aber man braucht oft
Bytefolgen.

> problematischen "F6" einfach ein "F600" wird. Ich kann mir nicht vorstellen,
> daß das Unicodekonsortium aus ehemaligen Nazis besteht, die die Deutsche
> Sprache so sehr bevorzugen, daß bei der Umwandlung "unicode mit
> 8-bit-zeichen" 

Es gibt kein Unicode mit 8-bit-Zeichen. Dein Tool lügt.

> Frage 3) <frage>Warum exceptions, wenn ich einen Unicode-String binär
> abspeichern möchte?</frage

Definiere "binär". Wenn Du "als Bytefolge" meinst: Mit welcher
Kodierung?

> >>> test
> u'l\xf6'
> >>> f = file("test.txt","wb")
> >>> f.write(test)
> Traceback (most recent call last):
>   File "<interactive input>", line 1, in ?
> UnicodeError: ASCII encoding error: ordinal not in range(128)

Wenn Du einen Unicode-String an einer Stelle übergibst, wo ein
Bytestring verlangt wird, wendet Python das "system encoding" an. Das
ist standardmäßig ASCII. "ascii" ist genauso wie "utf-16", mit
folgenden Unterschieden:
- jedes Zeichen wird in "ascii" mit einem Byte kodiert.
- "ascii" kann nur ein 512-tel von Unicode kodieren; die Umlaute sind
  nicht dabei.


> Warum "warum": ich habe "wb" angegeben, und würde erwarten, daß keine
> gesonderte Interpretation stattfindet, sondern einfach die Binärdaten
> weggeschrieben werden.

Einfach Binärdaten geht nicht. Unicode-Strings sind keine Bytefolgen,
sondern Zahlenfolgen. Du musst eine Kodierung wissen.

> >>> mit_dem_hammer_programmieren = lambda x: "".join(map(chr,map(ord,x)))
> >>> f.write(mit_dem_hammer_programmieren(test))
> 
> Und das ist ja genau das, was ich in diesem Fall machen möchte.

Ist es nicht. Auf Deinem Computer ist das Euro-Zeichen Byte 0x80, im
Unicode-String 0x20AC, also größer als 256. Was Du tatsächlich willst,
ist .encode("cp1252").

> OK, Scherz beiseite, natürlich sind Zahlen Zahlen. Das ändert aber
> nichts an meiner Aussage. Um deutlicher zu machen, was ich meine:

Schon, aber Bytes sind keine Zeichen.

> Der Computer (der Assembler) bearbeitet Bytes. Er kann auch Gruppen von 2
> oder 4 oder 8 Bytes bearbeiten, aber normalerweise bearbeitet er Bytes.

Nicht wenn er mit Unicode-Strings arbeitet.

> Daß das $f6 ein 'ö' sein soll ist eine Frage der *Interpretation*. Ich
> *interpretiere* die Bytes mit einer Codepage (mit einem Font)

Nicht, wenn es Unicode ist. Dann kannst Du nicht einzelne Bytes
interpretieren, sondern nur Zweiergruppen oder Vierergruppen.

MaW: Die Codepage hat 1114112 Zeichen.

> Jetzt gibt es Länder, in denen es sagen wir mal 2000 Schriftzeichen gibt
> (Japan vielleicht). Das ändert nichts daran, daß der Computer weiterhin
> stupide ein um das andere Byte hin-und-herschiebt.

Schon, nur ist es nicht sinnvoll, auf dieser Ebene zu denken. Damit
kann man gewisse Phänomene nicht einfach erklären: die Erklärungen
werden kompliziert, wenn man das auf der Byteebene erklären will.

Der Computer interpretiert ja schliesslich auch keine Bytes, sonder
Spannungs- und Magnetisierungszustände.

> Da wären wir wieder bei meiner "Unicode = Jedes Zeichen wird als zwei Bytes
> abgespeichert". 

Und genau das ist falsch. Die Ermittlung von Bytes in Unicode ist
mehrstufig:

Zeichen -> Codepunkt (Unicode)
Codepunkt -> Bytefolge (Encoding)

Von diesen beiden Interpretationsschritten fasst "Unicode" nur den
ersten, der zweite ist offen.

> Ich werde das Gefühl nicht los, daß die "wir basteln ein optimiertes
> Unicode-Encoding, wo 'viele' Zeichen als Bytes abgespeichert werden,
> und der komische europäische Sonderzeichenmüll aus zwei (oder IIRC
> gar drei) Zeichen abgesetzt wird" nicht völlig optimal ist, da sie
> die Komplexität der Unicodebehandlung gegenüber "Unicode = Jedes
> Zeichen wird als zwei Bytes abgespeichert" erheblich erhöht.

Genau. Keine Kodierung ist optimal. Diese Kodierung ist UTF-8, sie hat
folgende Vorteile:
- Kann alle Unicode-Zeichen kodieren
- In keinem Code kommen Nullbytes vor (ist ein Vorteil gegenüber UTF-16,
  wo durchaus Nullbytes vorkommen)
Nachteile:
- Verschiedene Zeichen benötigen verschiedene Länge

> Damit habe ich aber erstmal kein Problem, ich will ja einfach
> *meine* Daten von Windows nach Linux bringen, und ich möchte in
> diesem Zusammenhang nicht gleichzeitig meine Muttersprache von
> Deutsch auf Chinesisch ändern.

Dann solltest Du eine Kodierung verwenden, die auf beiden Systemen in
Verwendung ist. Leider gibt es keine solche. "cp1252" und "iso-8859-1"
sind sich genügend ähnlich, solange Du nur Umlaute benötigst.

> Äh, das Beispiel kann ich nicht ganz verstehen. Die Hexzahl 0xF63A ist genau
> eine Zahl (wenn man mal vom Thema Endianess absieht), und zwar z.B. Dual
> 11110110 00111010. Ich sehe nicht, wo ich da was umrechne. Vielleicht paßt
> die Zahl nicht in ein Byte, und ich muß deshalb zwei Bytes nehmen - es gibt
> aber eigentlich nur eine Umrechnung "zahl - byte"

Es gibt sehr viele Umrechungen:

>>> s=u"\uF63A"
>>> s.encode("utf-16le")
'\x3a\xf6'
>>> s.encode("utf-16be")
'\xf6\x3a'
>>> s.encode("utf-8")
'\xef\x98\xba'

Das sind schonmal drei Möglichkeiten, die Zahl F63A in Bytefolgen
umzuwandeln.

> Umrechnen tue ich, wenn ich die Information "ich möchte einen Kringel mit
> zwei Punkten drüber als einzelnes Zeichen ausgeben" als 0xF6 abspeichere.

Du machst hier zwei Schritte in einem:

Schritt 1: LATIN SMALL LETTER O WITH DIAERESIS wird auf Zahl 246 abgebildet.
Schritt 2: Zahl 246 wird in Bytefolge umgewandelt.

Unicode beschäftigt sich nur mit dem ersten Schritt.

> > Python nimmt per default an, daß Zeichen in
> > normalen Strings ASCII-kodiert sind. Umlaute gehören nicht
> > dazu. Alles andere als eine Exception, die ja schließlich zum
> > Nachdenken anregen soll, würde nur in Datenmüll resultieren.
> 
> Äh, das nimmt es bei *Unicode* an, nicht bei *normalen* Strings. Beispiel:

Das nimmt es an, wenn es zwischen Unicode-Strings und Bytestrings
umwandeln muss.

Ciao,
Martin




More information about the Python-de mailing list