[Python-de] Unicodefragen klarer gestellt

Gerson Kurz gerson.kurz at pergamon-software.de
Thu Nov 7 07:58:35 EST 2002


Also, vielleicht oder sogar mit an Sicherheit grenzender Wahrscheinlichkeit
war ich gestern nicht auf der Höhe meiner Gelassenheit und habe deshalb die
Ungelassenheit der Antworten provoziert.

Vorbemerkung: Ich benutze

PythonWin 2.2.2 (#37, Oct 14 2002, 17:02:34) [MSC 32 bit (Intel)] on win32.

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"

Das wäre übrigens die richtige Antwort auf meine Frage: "warum muß ich den
Unicode-String nochmal encoden" gewesen: weil das natürlich kein utf-16le
ist, die Datei aber utf-16le enthalten soll.

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?

Ursprünglich hing ich der Illusion an:

	Unicode = Jedes Zeichen wird als zwei Bytes abgespeichert.

Diese Illusion wurde genährt durch Unicode am Win32-API unter C: entweder
hat man Unicode nicht definiert, dann ist "CHAR char", oder man hat Unicode
definiert, dann ist "CHAR short"; sprich aus

CHAR szName[1860]

wird

short szName[1860]

usw. usf. Damit kommt man (gut, vielleicht nicht "man", aber zumindest: ich)
eigentlich auch relativ gut zurecht. Wenn ich mir aber

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, und nicht um plain old
"was kümmert mich der Chinese mir san Bayern" Bytes wie wir sie aus C-Code
kennen und lieben.

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

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

Beispiel:

>>> test = u"\x6c\xf6" # das "lö" von oben
>>> test
u'l\xf6'
>>> type(test)
<type 'unicode'>
>>> type(test.encode("utf-16le"))
<type 'str'>
>>>

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

output( hexdump(attrib) )
attrib = attrib.encode("utf-16le")
output( hexdump(attrib) )

und erhielt folgendes:

Dump of 26 Bytes (type <type 'unicode'>)
$00000000 4F72646E 65722022 47656CF6 73636874 65204F62  Ordner "Gel.schte Ob
$00000014 6A656B74 6522                                 jekte"

Dump of 52 Bytes (type <type 'str'>)
$00000000 4F007200 64006E00 65007200 20002200 47006500  O.r.d.n.e.r. .".G.e.
$00000014 6C00F600 73006300 68007400 65002000 4F006200  l...s.c.h.t.e. .O.b.
$00000028 6A006500 6B007400 65002200                    j.e.k.t.e.".

Leichte Unsicherheit beschleicht mich, wenn ich sehe, daß aus dem ach so
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" zu "unicode mit 16-bit-zeichen" der Bytecode beibehalten
wird, während sich der Franzmann mit komischen Riesenzeichen rumschlagen
muß. Oder? Um es noch genauer zu formulieren: daß aus "4F" ein "4F00" wird,
ist zu erwarten, weil "4F" im Bereich 7-Bit-ASCII liegt. Aber "F6" liegt ja
in der Grauzone 128-255, ich würde also erwarten, "F6" wird zu sagen wir
"1234" und nicht nur zu "F600".

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

Folgendes:

>>> 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)

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

>>> print map(ord,test)
[108, 246]

sehen, daß beide Zeichen "in ein Byte passen". Und daher übrigens auch
folgende Hardcore-Lösung für mein Problem

>>> 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.

* EINSCHUB: Zur letzteren Frage habe ich inzwischen eine Antwort in Marcs'
pdf-Vortrag gefunden: Es soll helfen, bei der Migration auf Unicode Fehler
zu identifizieren (""BDFL" entscheidet default encoding ist ASCII weil
"Helps identify the problem areas in programs").

Gut, ich hätte noch mehr Fragen dazu, aber ich will nicht zu sehr die Geduld
der geneigten Antworter provizieren, deshalb Schluß damit.

Jetzt noch vielleicht zu den Fragen und Antworten auf mein vorheriges
Schriftwerk.

Ich hatte geschrieben:

> Bytes sind Bytes. ... Ein Byte ist einfach nur eine Zahl.

Martin v. Loewis schreibt:

> Diese Kenntnisse erlischen natürlich in dem Moment, wo man mehr als
> 256 Zeichen haben will. Ab dann gilt: Zahlen sind Zahlen.

Falsch, Zahlen sind Mengen, wie Russell 1910 versucht hat nachzuweisen.

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

Der Computer (der Assembler) bearbeitet Bytes. Er kann auch Gruppen von 2
oder 4 oder 8 Bytes bearbeiten, aber normalerweise bearbeitet er Bytes.
Computer sind dumm und kommunistisch: jedes Byte ist gleich. Wenn das Byte
zufällig $f6 ist, dann ist das ein Byte wie andere - daß ich als Mensch
anstelle des $F6 ein 'ö' sehen will, ist mein humanes Problem, das kümmert
den Computer erstmal garnicht.

Daß das $f6 ein 'ö' sein soll ist eine Frage der *Interpretation*. Ich
*interpretiere* die Bytes mit einer Codepage (mit einem Font), so daß ich am
Bildschirm einen Kreis mit zwei Punkten drüber sehe - der Computer kennt
kein 'ö', der Computer kennt $f6.

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.

Wenn der Japaner sein Zeichen mit der Nummer 1860 abbilden will, dann kann
er das nicht in einem Byte machen, sondern muß dafür zwei Bytes hernehmen.

Da wären wir wieder bei meiner "Unicode = Jedes Zeichen wird als zwei Bytes
abgespeichert". 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.

Ich schrub:

> Du kannst die Bytes unter Windows wegschreiben, und unter Linux
> reinnudeln.

Martin v. Loewis schrab:

> Klar. Allerdings ist dann ein ü nicht mehr unbedingt ein ü.

Ja. Daß ist auch heute schon der Fall - das ist sogar der Fall, wenn ich
eine Datei von einem deutschen Windows auf ein deutsches OS/2 übertrage.
Zitat aus meinen C++-Quälcodes:

#ifdef TARGET_IS_OS2
#define AE "Ž"
#define ae "„"
#define OE "™"
#define oe "”"
#define UE "š"
#define ue ""
#define ss "á"
#elif defined(TARGET_IS_WIN32)
#define AE "Ä"
#define ae "ä"
#define OE "Ö"
#define oe "ö"
#define UE "Ü"
#define ue "ü"
#define ss "ß"
#endif

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.

Ich schrüb:

> Frage 1: Warum muß ich den "<type 'unicode'>" noch mal als Unicode encoden
?
> Der Laie würde sagen das ist doch redundant?

Martin v. Loewis schräb:

> Ganz einfach: Zahlen sind nicht Bytes. Zahlen sind Zahlen. Will man
> Zahlen in Bytes umrechnen, gibt es mehrere Möglichkeiten (utf-16be,
> utf-16le, utf-8, ...)

Ä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"

Umrechnen tue ich, wenn ich die Information "ich möchte einen Kringel mit
zwei Punkten drüber als einzelnes Zeichen ausgeben" als 0xF6 abspeichere.
Mein Fehler war die Illusion: Es gibt mit Unicode genau eine kanonische
Umrechnung "alle zeichen dieser Welt" <-> "Bytebatzen". Dazu merkt der
pdf-Vortrag von M.-A. Lemburg richtig an, daß es mehrere solche Umrechnungen
gibt.

M.-A. Lemburg ist ferner ein geschrieben Habender:

> 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:

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

Und an dieser Stelle wieder mein Verständnisproblem. Wenn überhaupt, hätte
ich mir die Exception bei einfachen Strings erwartet, und nicht bei Unicode.
Nach folgender Logik: der Einsatz von Unicode soll mich ja eigentlich
jeglichen Nachdenkens über Sanskrit entheben. Tatsächlich muß ich aber, um
Unicode sinnvoll einsetzen zu können, hier wie mir scheint erhebliche
Nachdenkarbeit leisten.

M.-A. Lemburg wird immer ein geschrieben Habender sein:

http://www.egenix.com/files/python/Unicode-EPC2002-Talk.pdf

Danke, das bringt mich schon ein bischen weiter. (Zum Beispiel habe ich da
gesehen, warum die Exception bei Unicode auftritt und nicht bei Strings).

Nun gut, das Problem ist identifiziert, fehlt nur noch die Lösung ;)

---------- Vorsicht, Codeschnipsel auf der Fahrbahn --------

def hexdump(data):
    global printable_chars
    addr, bytes, ascii = "$00000000", [' '] * 20, [' '] * 20
    result = cStringIO.StringIO()
    print >>result, "Dump of %d Bytes (type %s)" % (len(data), type(data))
    for i in range(len(data)):
        byte = ord(data[i])
        bytes[i%20], ascii[i%20] = "%02X" % byte, printable_chars[byte]
        if i%4 == 3: bytes[i%20] += " "
        if (i % 20) == 19:
            print >>result, addr, ''.join(bytes), ''.join(ascii)
            addr, bytes, ascii = "$%08X" % (i+1), [' '] * 20, [' '] * 20
    i = len(data)
    if i and (i % 20) <> 0:
        print >>result, addr, "%-45s" % ''.join(bytes).strip(),
''.join(ascii)
    return result.getvalue()

printable_chars = ['.'] * 256
for __c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
!\"$:;-#+*%&/()=?'":
    printable_chars[ord(__c)] = __c





More information about the Python-de mailing list