Wie Unicode String slicen?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
rhersel
User
Beiträge: 105
Registriert: Mittwoch 3. Dezember 2008, 11:29

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?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

rhersel hat geschrieben: Kann man einen Unicode-String zeichensicher zerschneiden?
Du hast ja keinen Unicode, sondern einen Byte-Code-String!

Code: Alles auswählen

s = u"äüö"
Probiers mal damit :-)
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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'.
rhersel
User
Beiträge: 105
Registriert: Mittwoch 3. Dezember 2008, 11:29

Danke. Ich habe es jetzt so gelöst:

Code: Alles auswählen

ucontent = unicode(mybytestring)
content = ucontent[:cut]
Das funktioniert für meine Zwecke.
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".
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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.
rhersel
User
Beiträge: 105
Registriert: Mittwoch 3. Dezember 2008, 11:29

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)
>>> 
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.
Benutzeravatar
mkesper
User
Beiträge: 919
Registriert: Montag 20. November 2006, 15:48
Wohnort: formerly known as mkallas
Kontaktdaten:

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?
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.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

sma hat geschrieben:Die "Combining Diacritical Marks" sind zwar Code Points, aber AFAIK keine Zeichen.
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?
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.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

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. ;)
Antworten