2016-02-15 30 views
18

Sto lavorando in Python 2 e ho una stringa contenente emoji e altri caratteri Unicode. Ho bisogno di convertirlo in una lista in cui ogni voce nella lista è un singolo personaggio/emoji.Estrarre correttamente Emojis da una stringa Unicode

x = u'xyz' 
char_list = [c for c in x] 

L'output desiderato è:

['', '', 'x', 'y', 'z', '', ''] 

L'uscita effettiva è:

[u'\ud83d', u'\ude18', u'\ud83d', u'\ude18', u'x', u'y', u'z', u'\ud83d', u'\ude0a', u'\ud83d', u'\ude0a'] 

Come posso ottenere l'output desiderato?

+0

L'ho chiuso come duplicato di una domanda sul superset. Passa attraverso la risposta loro chiaramente. Se ancora non risolve il tuo problema, per favore [modifica] il post per includere i tuoi ulteriori tentativi. –

+0

La mia domanda differisce dall'altra in quanto ho a che fare con stringhe che contengono un mix di emoji e personaggi non emoji. Inoltre, non sono interessato a contare gli emoji ma a ottenere un elenco di tutti i personaggi. – Aaron

+0

Per essere chiari, la lista che hai è corretta. È solo che se si stampa un 'elenco' mostra il' repr' del contenuto, non la forma 'str'; è necessario stampare manualmente le singole voci per vedere la forma 'str' (che sembrerebbe un'emoji). Ad esempio, se si esegue 'print (u ',' .join (char_list))' vedrete cosa vi aspettate senza parentesi iniziali o finali. – ShadowRanger

risposta

15

Prima di tutto, in Python2, è necessario utilizzare le stringhe Unicode (u'<...>') per i caratteri Unicode da visualizzare come caratteri Unicode. E correct source encoding se si desidera utilizzare gli stessi caratteri anziché la rappresentazione \UXXXXXXXX nel codice sorgente.

Ora, secondo Python: getting correct string length when it contains surrogate pairs e Python returns length of 2 for single Unicode character string, in python2 "stretta" build (con sys.maxunicode==65535), caratteri Unicode a 32 bit sono rappresentati come surrogate pairs, e questo non è trasparente alle funzioni di stringa. Questo è stato corretto solo in 3.3 (PEP0393).

La risoluzione più semplice (salvare per migrare a 3.3+) è compilare una compilazione "wide" di Python dal sorgente come indicato nel terzo link. In esso, i caratteri Unicode sono tutti a 4 byte (quindi sono un potenziale maiale della memoria) ma se è necessario gestire in modo sistematico caratteri larghi Unicode, questo è probabilmente un prezzo accettabile.

La soluzione per una "stretta" costruire è per fare un set personalizzato di funzioni di stringa (len, slice, forse come una sottoclasse di unicode), che rileva le coppie di surrogati e gestirli come un singolo carattere.Non ho potuto facilmente trovare uno esistente (che è strano), ma non è troppo difficile da scrivere:

  • secondo UTF-16#U+10000 to U+10FFFF - Wikipedia,
    • il 1 ° carattere (surrogato alto) è nella gamma 0xD800..0xDBFF
    • il 2 ° carattere (surrogato basso) - nella gamma 0xDC00..0xDFFF
    • questi intervalli sono riservati e quindi non può verificarsi come personaggi regolari

Quindi, ecco il codice per rilevare una coppia di surrogati:

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 

e una funzione che restituisce una semplice fetta:

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] 

Qui, il prezzo da pagare è la prestazione , poiché queste funzioni sono molto più lente dei built-in:

>>> 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 
+2

Nota che con tutte le recenti aggiunte fantastiche alle emoji questo è leggermente rotto, dato che alcune emoji sono costituite da più punti di codice. Gli esempi includono flag ('" "') e varianti etniche ('" "" vs '" "'), e alcune altre cose come combinare i segni diacritici '" à "'. – roeland

+0

@roeland quindi "is_surrogate" deve essere aggiornato per rilevare anche questi e restituire il numero di parole aggiuntive (= caratteri a 2 byte) anziché True/False. Questo ci ha permesso di essere interessati a questi casi (i caratteri di controllo e i segni diacritici sono una cosa completamente diversa se me lo chiedi) e altre strutture come la normalizzazione non possono svolgere il compito. –

+2

Non penso che la normalizzazione gestirà quelle emoticon. La risposta strettamente corretta avrebbe iterato su grapheme cluster, una spiegazione lunga e arcana in [Unicode® Standard Annex # 29] (http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules). Ma senza una libreria in grado di gestirmi probabilmente mi limiterei ad iterare su punti di codice. – roeland

8

vorrei utilizzare la libreria uniseg (pip install uniseg):

# -*- coding: utf-8 -*- 
from uniseg import graphemecluster as gc 

print list(gc.grapheme_clusters(u'xyz')) 

uscite [u'\U0001f618', u'\U0001f618', u'x', u'y', u'z', u'\U0001f60a', u'\U0001f60a'], e

[x.encode('utf-8') for x in gc.grapheme_clusters(u'xyz'))] 

fornirà l'elenco di caratteri come stringhe UTF-8 codificati.

+1

La tua risposta non stampa l'output desiderato – otorrillas

+1

Ok, aggiungerò la conversione per fornire esattamente quale sia la domanda. –

+0

@James Hopkin puoi fornire un modo per convertire questi emoji in unicode come in u '\ U0001f618' in python 3 –