2010-11-24 22 views
10

Estoy trabajando en contra de una aplicación que parece interesada en devolver, lo que creo que es, cadenas dobles codificadas en UTF-8.Decodificación doble en unicode en python

Envío la cadena u'XüYß' codificada usando UTF-8, convirtiéndose así en X\u00fcY\u00df (igual a X\xc3\xbcY\xc3\x9f).

El servidor debe simplemente repetir lo que la envié, sin embargo, devuelve lo siguiente: X\xc3\x83\xc2\xbcY\xc3\x83\xc2\x9f (debe ser X\xc3\xbcY\xc3\x9f). Si lo decodifico usando str.decode('utf-8') se convierte en u'X\xc3\xbcY\xc3\x9f', que se ve como ... unicode-cadena, que contiene la cadena original codificada usando UTF-8.

pero Python no me permite descodificar una cadena Unicode sin volver a codificar en primer lugar - la cual falla por alguna razón, que se me escapa:

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

¿Cómo persuadir Python para volver a decodificar la cadena ? - y/o hay alguna forma (práctica) de depurar lo que está realmente en las cadenas, sin pasarlo a través de todas las conversiones implícitas print?

(Y sí, me han informado de este comportamiento con los desarrolladores del lado del servidor.)

Respuesta

19

ret.decode() intentos implícitamente codificar ret con el codificación del sistema - en su caso, ascii.

Si codifica explícitamente la cadena Unicode, debería estar bien. Hay una codificación incorporada que hace lo que necesita:

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

Realmente, .encode('latin1') (o CP1252) estaría bien, porque eso es lo que el servidor está casi cerainly utilizando. El códec raw_unicode_escape sólo le dará algo reconocible al final en lugar de lanzar una excepción:

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

En caso de que tenga este tipo de datos mixtos, puede utilizar el códec de nuevo, para normalizar todo:

>>> '€\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 ** - no necesita usar mi cosa de miedo. –

0

No utilice este! Use @hop's solution.

Mi truco desagradable: (encogerse pero en silencio No es mi culpa, es culpa de los desarrolladores del servidor!.)

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

Entonces,

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

Una gran pregunta, por cierto. Una situación desagradable. Espero que alguien más pueda encontrar una solución más ordenada que 'chr (ord (c))' para convertir unicode en str, carácter por carácter ... –

+0

'f (char) para char en gritos de cuerda para una codificación. – hop

+0

@hop: ¿verdad? ¿Cómo es eso? –

1

lo que se quiere es la codificación Unicode, donde X punto de código se codifica con el mismo valor de byte X. para los puntos de código dentro de 0-255 que tiene esto en el latin-1 de codificación:

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