2010-11-24 5 views
10

Je travaille sur une application qui semble vouloir retourner, ce que je crois être, des chaînes codées en UTF-8.Unicode à double décodage en python

J'envoie la chaîne u'XüYß' codée en utilisant UTF-8, devenant ainsi X\u00fcY\u00df (égale à X\xc3\xbcY\xc3\x9f).

Le serveur doit simplement l'écho ce que je l'ai envoyé, mais renvoie les éléments suivants: X\xc3\x83\xc2\xbcY\xc3\x83\xc2\x9f (devrait être X\xc3\xbcY\xc3\x9f). Si je le décode en utilisant str.decode('utf-8') devient u'X\xc3\xbcY\xc3\x9f', ce qui ressemble à un ... unicode-string, contenant la chaîne d'origine encodée en utilisant UTF-8.

Mais Python ne me laisse pas décoder une chaîne de caractères Unicode sans réencodage premier - qui échoue pour une raison, qui me échappe:

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

Comment puis-je persuade Python de re-décoder la chaîne ? - et/ou existe-t-il un moyen (pratique) de déboguer ce qui est réellement dans les chaînes, sans le passer par toutes les conversions implicites print?

(Et oui, j'ai rapporté ce comportement avec les développeurs du côté serveur.)

Répondre

19

ret.decode() tente implicitement encode ret avec le codage du système - dans votre cas ASCII.

Si vous codez explicitement la chaîne unicode, ça devrait aller. Il y a un codage builtin qui fait ce que vous avez besoin:

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

Vraiment, .encode('latin1') (ou CP1252) serait OK, parce que ce serveur est presque en utilisant cerainly. Le codec raw_unicode_escape va vous donner quelque chose de reconnaissable à la fin au lieu de soulever une exception:

>>> '€\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) 

Si vous rencontrez ce type de données mixtes, vous pouvez utiliser à nouveau le codec, pour tout normaliser:

>>> '€\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

** Whew ** - n'a pas besoin d'utiliser ma chose effrayante. –

0

Ne pas utiliser cela! Utilisez @hop's solution.

Mon méchant bidouille: (grincer des dents, mais tranquillement Ce n'est pas ma faute, c'est le serveur de la faute des développeurs!.)

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

Ensuite,

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

Bonne question, au fait. Une situation désagréable. J'espère que quelqu'un d'autre peut trouver une solution plus simple que 'chr (ord (c))' pour convertir unicode en str, caractère par caractère ... –

+0

'f (char) pour char dans les' 'cris '' pour un encodage. – hop

+0

@hop: le fait-il? Comment? –

1

ce que vous voulez est l'encodage où le point de code Unicode X est codé à la même valeur d'octet X. pour les points de code à l'intérieur 0-255 vous avez ce dans le codage latin-1:

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