python - Extrae correctamente Emojis de una cadena Unicode
python-2.x (2)
Estoy trabajando en Python 2 y tengo una cadena que contiene emojis y otros caracteres Unicode. Necesito convertirlo a una lista donde cada entrada de la lista sea un solo carácter / emoji.
x = u''đđxyzđđ''
char_list = [c for c in x]
El resultado deseado es:
[''đ'', ''đ'', ''x'', ''y'', ''z'', ''đ'', ''đ'']
La salida real es:
[u''/ud83d'', u''/ude18'', u''/ud83d'', u''/ude18'', u''x'', u''y'', u''z'', u''/ud83d'', u''/ude0a'', u''/ud83d'', u''/ude0a'']
¿Cómo puedo lograr el resultado deseado?
Utilizaría la biblioteca uniseg ( pip install uniseg
):
# -*- coding: utf-8 -*-
from uniseg import graphemecluster as gc
print list(gc.grapheme_clusters(u''đđxyzđđ''))
salidas [u''/U0001f618'', u''/U0001f618'', u''x'', u''y'', u''z'', u''/U0001f60a'', u''/U0001f60a'']
, y
[x.encode(''utf-8'') for x in gc.grapheme_clusters(u''đđxyzđđ''))]
proporcionará la lista de caracteres como cadenas codificadas UTF-8.
En primer lugar, en Python2, debe utilizar cadenas Unicode ( u''<...>''
) para que los caracteres Unicode se vean como caracteres Unicode. Y corrija la codificación de origen si desea usar los caracteres en sí mismos en lugar de la representación /UXXXXXXXX
en el código fuente.
Ahora, según Python: obtener la longitud de cadena correcta cuando contiene pares de sustitución y Python devuelve una longitud de 2 para cadena de caracteres Unicode , en las versiones "estrechas" de sys.maxunicode==65535
(con sys.maxunicode==65535
), se representan caracteres Unicode de 32 bits como pares de sustitución , y esto no es transparente para las funciones de cadena. Esto solo se ha corregido en 3.3 ( PEP0393 ).
La resolución más simple (salvo para migrar a 3.3+) es compilar una compilación "amplia" de Python desde la fuente como se indica en el tercer enlace. En él, los caracteres Unicode son todos de 4 bytes (por lo tanto, son un potencial hog de la memoria), pero si necesita manejar rutinariamente caracteres anchos Unicode, este es probablemente un precio aceptable.
La solución para una compilación "estrecha" es hacer un conjunto personalizado de funciones de cadena ( len
, slice
, tal vez como una subclase de unicode
) que detecte los pares de sustitución y los maneje como un solo carácter. No pude encontrar fácilmente uno existente (lo cual es extraño), pero no es demasiado difícil de escribir:
- según UTF-16 # U + 10000 a U + 10FFFF - Wikipedia ,
- el primer carácter (sustituto alto) está en el rango
0xD800..0xDBFF
- el segundo carácter (sustituto bajo) - en el rango
0xDC00..0xDFFF
- estos rangos están reservados y por lo tanto no pueden ocurrir como caracteres regulares
- el primer carácter (sustituto alto) está en el rango
Así que aquí está el código para detectar un par suplente:
def is_surrogate(s,i):
if 0xD800 <= ord(s[i]) <= 0xDBFF:
try:
l = s[i+1]
except IndexError:
return False
if 0xDC00 <= ord(l) <= 0xDFFF:
return True
else:
raise ValueError("Illegal UTF-16 sequence: %r" % s[i:i+2])
else:
return False
Y una función que devuelve un simple segmento:
def slice(s,start,end):
l=len(s)
i=0
while i<start and i<l:
if is_surrogate(s,i):
start+=1
end+=1
i+=1
i+=1
while i<end and i<l:
if is_surrogate(s,i):
end+=1
i+=1
i+=1
return s[start:end]
Aquí, el precio que paga es el rendimiento, ya que estas funciones son mucho más lentas que las integradas:
>>> ux=u"a"*5000+u"/U00100000"*30000+u"b"*50000
>>> timeit.timeit(''slice(ux,10000,100000)'',''from __main__ import slice,ux'',number=1000)
46.44128203392029 #msec
>>> timeit.timeit(''ux[10000:100000]'',''from __main__ import slice,ux'',number=1000000)
8.814016103744507 #usec