2010-11-24 12 views
10

Ich arbeite gegen eine Anwendung, die darauf scharf zu kehren scheint, was ich glaube, doppelte UTF-8-codierte Strings.Doppeldecodierung Unicode in Python

Ich sende die Zeichenfolge u'XüYß' kodiert mit UTF-8, so dass X\u00fcY\u00df (gleich X\xc3\xbcY\xc3\x9f).

Der Server sollte einfach wiederholen, was ich es sendete, noch gibt Folgendes zurück: X\xc3\x83\xc2\xbcY\xc3\x83\xc2\x9f (sollte X\xc3\xbcY\xc3\x9f sein). Wenn ich es mit str.decode('utf-8') entzifferne, wird u'X\xc3\xbcY\xc3\x9f', die aussieht wie ein ... Unicode-String, enthält die ursprüngliche Zeichenfolge mit UTF-8 codiert.

Aber Python lässt mich nicht ein Unicode-String dekodieren ohne Umcodierung es zuerst - die aus irgendeinem Grund fehlschlägt, die mich entkommt:

>>> ret = 'X\xc3\x83\xc2\xbcY\xc3\x83\xc2\x9f'.decode('utf-8') 
>>> ret 
u'X\xc3\xbcY\xc3\x9f' 
>>> ret.decode('utf-8') 
# Throws UnicodeEncodeError: 'ascii' codec can't encode ... 

Wie kann ich Python überzeugen die Zeichenfolge erneut zu entschlüsseln ? - und/oder gibt es eine (praktische) Art zu debuggen, was tatsächlich in den Strings ist, ohne es zu übergeben, obwohl die gesamte implizite Konvertierung print verwendet?

(Und ja, habe ich dieses Verhalten mit den Entwicklern der Server-Seite angegeben.)

Antwort

19

ret.decode() versucht implizit ret mit der kodieren Systemcodierung - in Ihrem Fall ascii.

Wenn Sie die Unicode-Zeichenfolge explizit codieren, sollten Sie in Ordnung sein. Es gibt eine builtin das tut, was Sie brauchen:

>>> 'X\xc3\xbcY\xc3\x9f'.encode('raw_unicode_escape').decode('utf-8') 
'XüYß' 

Wirklich, .encode('latin1') (oder cp1252) würde in Ordnung sein, denn das ist, was der Server fast cerainly verwendet. Der raw_unicode_escape Codec wird einfach erkennbar etwas geben, am Ende statt eine Ausnahme zu heben:

>>> '€\xe2\x82\xac'.encode('raw_unicode_escape').decode('utf8') 
'\\u20ac€' 

>>> '€\xe2\x82\xac'.encode('latin1').decode('utf8') 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
UnicodeEncodeError: 'latin-1' codec can't encode character '\u20ac' in position 0: ordinal not in range(256) 

Falls Sie in diese Art von gemischten Daten ausführen, können Sie den Codec verwenden wieder, alles zu normalisieren:

>>> '€\xe2\x82\xac'.encode('raw_unicode_escape').decode('utf8') 
'\\u20ac€' 

>>> '\\u20ac€'.encode('raw_unicode_escape') 
b'\\u20ac\\u20ac' 
>>> '\\u20ac€'.encode('raw_unicode_escape').decode('raw_unicode_escape') 
'€€' 
+0

** Puh ** - brauche nicht meine gruselige Sache zu benutzen. –

0

dies nicht verwenden! Verwenden Sie @hop's solution.

Mein gemeiner Hack: (erschaudern, aber leise Es ist nicht meine Schuld, es ist die Schuld der Server-Entwickler!.)

def double_decode_unicode(s, encoding='utf-8'): 
    return ''.join(chr(ord(c)) for c in s.decode(encoding)).decode(encoding) 

Dann

>>> double_decode_unicode('X\xc3\x83\xc2\xbcY\xc3\x83\xc2\x9f') 
u'X\xfcY\xdf' 
>>> print _ 
XüYß 
+0

Große Frage, nebenbei bemerkt. Eine unangenehme Situation. Ich hoffe, jemand anderes kann mit einer besseren Lösung als 'chr (ord (c))' kommen, um Unicode nach String zu konvertieren, Zeichen für Zeichen ... –

+0

'f (char) für char in string' schreit nach einer Kodierung. – hop

+0

@ hop: geht das? Wie das? –

1

Was Sie wollen, ist die Codierung in der Unicode-Codepunkt X zu dem gleichen Byte-Wert X. Für Codepunkte innerhalb 0-255 dies Sie haben in der Latin-1 Codierung codiert ist:

def double_decode(bstr): 
    return bstr.decode("utf-8").encode("latin-1").decode("utf-8")