Seite 1 von 1
Wie Unicode String slicen?
Verfasst: Montag 19. April 2010, 15:42
von rhersel
Wie kann ich einen Unicode-String die ersten x Zeichen bekommen ohne Gefahr zu laufen, dass ich ein Unicode-Zeichen in der Mitte durchschneide?
Beispiel:
Code: Alles auswählen
>>> s = "äöü"
>>> s
'\xc3\xa4\xc3\xb6\xc3\xbc'
>>> x = s[3:]
>>> x
'\xb6\xc3\xbc'
>>>
Der String x lässt sich jetzt nicht mehr ohne Pango-Warning ausgeben:
PangoWarning: Invalid UTF-8 string passed to pango_layout_set_text()
Kann man einen Unicode-String zeichensicher zerschneiden?
Re: Wie Unicode String slicen?
Verfasst: Montag 19. April 2010, 15:54
von Hyperion
rhersel hat geschrieben:
Kann man einen Unicode-String zeichensicher zerschneiden?
Du hast ja keinen Unicode, sondern einen Byte-Code-String!
Probiers mal damit

Verfasst: Montag 19. April 2010, 15:55
von Darii
rhersel hat geschrieben:Kann man einen Unicode-String zeichensicher zerschneiden?
Nein, nicht ohne beträchtlichen Aufwand.
Allerdings verwendest du auch keine Unicode-Strings sondern utf8-codierte bytestrings. Mit u"äöü" kommst du schonmal weiter. Dann schneidest du meistens keine Zeichen auseinander.
Kann aber trotzdem passieren, wenn das Zeichen außerhalb der Basic Multilingual Plane liegt und Python mit ucs-2 compilliert wurde oder es ein zusammengesetztes Zeichen war(z.B. u'a\u0308'.
Verfasst: Montag 19. April 2010, 22:22
von rhersel
Danke. Ich habe es jetzt so gelöst:
Code: Alles auswählen
ucontent = unicode(mybytestring)
content = ucontent[:cut]
Das funktioniert für meine Zwecke.
Verfasst: Montag 19. April 2010, 23:00
von BlackJack
@rhersel: Wenn *das* wirklich funktioniert, dann hätte es auch mit `mybytestring` direkt funktionieren müssen. Das jetzt kracht nämlich sobald der Bytestring etwas ausserhalb von ASCII enthält. Reiner ASCII-Inhalt lässt sich aber problemlos "slicen".
Verfasst: Dienstag 20. April 2010, 22:16
von sma
Allgemein kann man sich auch in der Basic-Plane NICHT sicher sein, dass Python-Unicode-Strings schneidbar sind. Man schaue sich dieses Beispiel (unter Python 3.x) an:
Code: Alles auswählen
s = b"a\xcc\x88".decode("utf-8")
print(len(s))
print(ord(s[1]))
Der String ist ein einzelnes "ä", doch es wird in einer alternativen Kodierung bestehend aus "a" und Umlaut ("¨") zusammengesetzt. Das erkennt "len" aber nicht und liefert 2 statt 1.
Stefan
Verfasst: Dienstag 20. April 2010, 23:09
von Darii
sma hat geschrieben:Allgemein kann man sich auch in der Basic-Plane NICHT sicher sein, dass Python-Unicode-Strings schneidbar sind. Man schaue sich dieses Beispiel (unter Python 3.x) an:
Sag ich ja
Der String ist ein einzelnes "ä", doch es wird in einer alternativen Kodierung bestehend aus "a" und Umlaut ("¨") zusammengesetzt. Das erkennt "len" aber nicht und liefert 2 statt 1.
Naja, sind ja auch zwei Zeichen. Das mit den 4-byte-Zeichen finde ich viel schlimmer, da wird einem wirklich ein Zeichen als zwei verkauft. Ich würde das durchaus als Bug bezeichnen.
Verfasst: Dienstag 20. April 2010, 23:35
von rhersel
Hier ist ein Ausschnitt aus der Datei:
g38
asg50
(lp337
sg52
(lp338
S'Im Elektromixer mit viel Eis (zerstossenes oder W\xc3\xbcrfeleis) mixen und mit dem Eis in das Cocktailglas geben. Deko: frei'
p339
aS'2010.04.18, 18:09:40'
p340
ag65
asg72
In der Mitte ist der böse Teil zu sehen: Würfeleis.
Die Datei ist mit Python gepickelt und ohne Konvertierung mit pickle.load wieder eingelesen worden. Wenn ich den String mit dem Würfeleis auf 50 slice und in einer Listview-Zelle darstelle wird als letztes Zeichen ein Kasten mit Kreuz drin gezeigt (das ist wohl das halbe Unicode Zeichen ü). Wenn ich den String vor dem slicen mit unicode() umwandle wird das ü korrekt angezeigt.
Im interaktiven Modus kann ich es allerdings auch nicht nachvollziehen. Dann kommt das hier raus:
Code: Alles auswählen
>>> a = "Im Elektromixer mit viel Eis (zerstossenes oder W\xc3\xbcrfeleis)"
>>> a
'Im Elektromixer mit viel Eis (zerstossenes oder W\xc3\xbcrfeleis)'
>>> b = a[:50]
>>> b
'Im Elektromixer mit viel Eis (zerstossenes oder W\xc3'
>>> print b
Im Elektromixer mit viel Eis (zerstossenes oder W
>>> c = unicode(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 49: ordinal not in range(128)
>>>
Verfasst: Dienstag 20. April 2010, 23:52
von BlackJack
@rhersel: Natürlich kracht das. `unicode()` weiss ja nicht in welcher Kodierung die (Byte-)Zeichenkette vorliegt und beschwert sich wenn es nicht komplett ASCII ist. Und das sollte nicht nur interaktiv so sein. Du musst halt die Kodierung mit angeben.
Verfasst: Mittwoch 21. April 2010, 07:46
von mkesper
sma hat geschrieben:Allgemein kann man sich auch in der Basic-Plane NICHT sicher sein, dass Python-Unicode-Strings schneidbar sind. Man schaue sich dieses Beispiel (unter Python 3.x) an:
Code: Alles auswählen
s = b"a\xcc\x88".decode("utf-8")
print(len(s))
print(ord(s[1]))
Der String ist ein einzelnes "ä", doch es wird in einer alternativen Kodierung bestehend aus "a" und Umlaut ("¨") zusammengesetzt. Das erkennt "len" aber nicht und liefert 2 statt 1.
Stefan
Das sollte doch ein Bug sein, oder?
Verfasst: Mittwoch 21. April 2010, 08:18
von BlackJack
@mkesper: Ich würde mal nein sagen. Die Auswirkungen die das auf die Laufzeit hätte solche Fälle "korrekt" zu berücksichtigen, möchte ich zumindest nicht tragen. "Korrekt" übrigens in Anführungsstrichen weil es noch mehr solcher Fälle gibt, und die Frage wie man da konkret verfahren sollte, vom Anwendungsgebiet abhängen. Wenn man das "fixed" kommt nämlich der nächste angerannt und sagt das ist ein Bug und möchte gerne das alte Verhalten wiederhaben. Unicode-Slices passiert eben nicht auf Zeichen sondern auf Codepoints. Wer mehr braucht, muss sich nach einer passenden Bibliothek umsehen.
Verfasst: Mittwoch 21. April 2010, 08:44
von Darii
mkesper hat geschrieben:Das sollte doch ein Bug sein, oder?
Nein, sind ja zwei verschiedene Zeichen/Codepoints. Ein wirklicher Bug ist nur, dass 4-byte-Zeichen(z.B. u'\U0001d11e') im Standard(UCS2)-Build von Python eine Länge von 2 haben. Da wird dann nämlich wirklich ein einzelner Codepoint auseinandergerupft.
Verfasst: Mittwoch 21. April 2010, 09:06
von sma
Die "
Combining Diacritical Marks" sind zwar Code Points, aber AFAIK keine Zeichen. Das U+0308 aus meinem Beispiel wird zu U+00A8, wenn es einzeln dargestellt werden muss.
Die Lösung ist, zu normalisieren, bevor man Strings zerlegt.
Code: Alles auswählen
# python 3.x
import unicodedata
s = "a\u0308"
print(len(s))
print(len(unicodedata.normalize("NFC", s)))
s = "\u00e4"
print(len(s))
print(len(unicodedata.normalize("NFD", s)))
Stefan
Verfasst: Mittwoch 21. April 2010, 09:21
von Darii
Laut Standard werden sie zumindest als "Characters" bezeichnet.

Aber ist ja für das Verhalten egal, da man ja in Python mit den Codepoints arbeitet und nicht den Zeichen.
Die Lösung ist, zu normalisieren, bevor man Strings zerlegt.
Erwischt man damit wirklich alles?
Verfasst: Mittwoch 21. April 2010, 09:28
von BlackJack
@Darii: Da muss man wohl Typographie und Computer-Jargon auseinanderhalten. Bei Computern wird auch der Bytewert 7 in der ASCII-Kodierung als "character" bezeichnet. Aber klingeln oder piepsen ist für mich eher ein Ton als ein Zeichen.
Mit dem Normalisieren erwischt man nicht alles weil nicht alle möglichen Kombinationen auch "precomposed" existieren.
Verfasst: Mittwoch 21. April 2010, 09:56
von Darii
BlackJack hat geschrieben:@Darii: Da muss man wohl Typographie und Computer-Jargon auseinanderhalten. Bei Computern wird auch der Bytewert 7 in der ASCII-Kodierung als "character" bezeichnet. Aber klingeln oder piepsen ist für mich eher ein Ton als ein Zeichen.

Das tue ich. Deswegen sind es
Zeichen. Auch ASCII Nr. 7 ist ein Zeichen, es heißt in Unicode BELL. C-chars haben damit erstmal wirklich nichts zu tun. Aber das wird jetzt leicht OT.
