utf8 type python python-3.x unicode surrogate-pairs

type - ¿Cómo trabajar con pares sustitutos en Python?



type unicode python (2)

Este es un seguimiento de la conversión a Emoji . En esa pregunta, el OP tenía un archivo codificado con json.dumps() con un emoji representado como un par suplente - /ud83d/ude4f . Estaba teniendo problemas para leer el archivo y traducir el emoji correctamente, y la answer correcta era json.loads() cada línea del archivo, y el módulo json manejaría la conversión del par de sustitutos de nuevo a (supongo UTF8-codificado) emoji.

Así que aquí está mi situación: digamos que solo tengo una cadena unicode Python 3 normal con un par suplente en ella:

emoji = "This is /ud83d/ude4f, an emoji."

¿Cómo puedo procesar esta cadena para obtener una representación del emoji fuera de ella? Estoy buscando algo como esto:

"This is 🙏, an emoji." # or "This is /U0001f64f, an emoji."

He intentado:

print(emoji) print(emoji.encode("utf-8")) # also tried "ascii", "utf-16", and "utf-16-le" json.loads(emoji) # and `.encode()` with various codecs

En general, UnicodeEncodeError: XXX codec can''t encode character ''/ud83d'' in position 8: surrogates no allowed un error similar a UnicodeEncodeError: XXX codec can''t encode character ''/ud83d'' in position 8: surrogates no allowed .

Estoy ejecutando Python 3.5.1 en Linux, con $LANG configurado en en_US.UTF-8 . He ejecutado estas muestras tanto en el intérprete de Python en la línea de comandos como en IPython ejecutándose en texto sublime. No parece haber diferencias.


Debido a que esta es una pregunta recurrente y el mensaje de error es un poco oscuro, aquí hay una explicación más detallada.

Los sustitutos son una forma de expresar puntos de código Unicode más grandes que U + FFFF.

Recuerde que originalmente se especificó que Unicode contenía 65.536 caracteres, pero que pronto se descubrió que esto no era suficiente para acomodar a todos los glifos del mundo.

Como un mecanismo de extensión para la codificación UTF-16 (de lo contrario, de ancho fijo), se configuró un área reservada para contener un mecanismo para expresar puntos de código fuera del Plano Multilingüe Básico : Cualquier punto de código en esta área especial deberá ser seguido por otro código de carácter de la misma área, y juntos, expresarían un punto de código con un número mayor que el límite anterior.

(Estrictamente hablando, el área de los sustitutos se divide en dos mitades; el primer sustituto en un par debe provenir de la mitad de los sustitutos altos, y el segundo, de los sustitutos bajos).

Este es un mecanismo heredado para admitir la codificación UTF-16 específicamente, y no debe usarse en otras codificaciones.

En otras palabras, mientras que U+12345 puede expresarse con el par suplente U + D808 U + DF45, simplemente debe expresarlo directamente en su lugar.

Más detalladamente, aquí está cómo se expresaría esto en UTF-8 como un solo carácter:

0xF0 0x92 0x8D 0x85

Y aquí está la secuencia sustituta correspondiente:

0xED 0xA0 0x88 0xED 0xBD 0x85

Como ya se sugirió en la respuesta aceptada, puede realizar un viaje de ida y vuelta con algo como

>>> "/ud808/udf45".encode(''utf-16'', ''surrogatepass'').decode(''utf-16'').encode(''utf-8'') b''/xf0/x92/x8d/x85''

Quizás también vea http://www.russellcottrell.com/greek/utilities/surrogatepaircalculator.htm


Ha mezclado una cadena literal /ud83d en un archivo json en el disco (seis caracteres: / ud 8 3 d ) y un solo carácter u''/ud83d'' (especificado usando un literal de cadena en el código fuente de Python) en la memoria. Es la diferencia entre len(r''/ud83d'') == 6 y len(''/ud83d'') == 1 en Python 3.

Si ves ''/ud83d/ude4f'' cadena de Python ( 2 caracteres), entonces hay un error en el sentido ascendente. Normalmente, no deberías obtener tal cadena. Si obtienes uno y no puedes solucionar el flujo ascendente que lo genera; podría arreglarlo usando el controlador de errores surrogatepass :

>>> "/ud83d/ude4f".encode(''utf-16'', ''surrogatepass'').decode(''utf-16'') ''🙏''

Python 2 era más permisivo .

Nota: incluso si su archivo json contiene literal / ud83d / ude4f ( 12 caracteres); usted no debe obtener el par sustituto:

>>> print(ascii(json.loads(r''"/ud83d/ude4f"''))) ''/U0001f64f''

Aviso: el resultado es 1 carácter ( ''/U0001f64f'' ), no el par suplente ( ''/ud83d/ude4f'' ).