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'')
''🙏''
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''
).