2012-10-16 8 views
13

Si consideri il seguente scambio sui IPython:Python: ottenere corretta lunghezza della stringa quando contiene le coppie di surrogati

In [1]: s = u'華袞與緼同歸' 

In [2]: len(s) 
Out[2]: 8 

L'output corretto avrebbe dovuto essere 7, ma perché il quinto di questi sette caratteri cinesi ha un codice di alta Unicode -point, è rappresentato in UTF-8 da una "coppia surrogata", piuttosto che da un semplice punto di codice, e come risultato Python pensa che siano due caratteri anziché uno.

Anche se io uso unicodedata, che restituisce la coppia di surrogati correttamente come un unico codepoint (\U00026177), quando passa alla len() della lunghezza sbagliata è ancora restituito:

In [3]: import unicodedata 

In [4]: unicodedata.normalize('NFC', s) 
Out[4]: u'\u83ef\u889e\u8207\u7dfc\U00026177\u540c\u6b78' 


In [5]: len(unicodedata.normalize('NFC', s)) 
Out[5]: 8 

Senza prendere misure drastiche come ricompilare Python per UTF-32, c'è un modo semplice per ottenere la lunghezza corretta in situazioni come questa?

Sono su IPython 0.13, Python 2.7.2, Mac OS 10.8.2.

+0

Le discussioni [qui] (http://stackoverflow.com/questions/9934752/platform-specific-unicode-semantics-in-python-2-7) e [qui] (http://stackoverflow.com/ questions/6922480/how-to-get-a-reliable-unicode-character-count-in-python) sembrano rilevanti. – DSM

+0

@DSM: Grazie per averli scoperti. Il tuo primo link mostra Python compilato per UTF-32 ("wide build"), qualcosa che ho escluso nella mia domanda. Nel secondo, la risposta di wberry mostra un elaborato codice per contare effettivamente i veri personaggi. La mia soluzione predefinita è come quest'ultima, ma spero che esista qualcosa di integrato e più diretto. – brannerchinese

+0

Non riesco a riprodurre qui il risultato (Ubuntu box, python 2.7.2). Per l'unicode u '\ u83ef \ u89i \ u8207 \ u7dfc \ U00026177 \ u540c \ u6b78' ottengo una lunghezza di sette con entrambi len (s) e len (unicode.normalize ('NFC', s)) – Vicent

risposta

7

Penso che questo sia stato risolto in 3.3. Vedere:

http://docs.python.org/py3k/whatsnew/3.3.html
http://www.python.org/dev/peps/pep-0393/ (cercare wstr_length)

+0

Sì. Ma nel 2.7 siamo apparentemente da soli, a meno che non usiamo una build estesa. Sfortunatamente ci vorrà un po 'prima che possa passare a Py3. – brannerchinese

+1

Mi sono trasferito a Py3 a febbraio, e (eccetto quando sono forzato di nuovo in 2.7 da librerie come NLTK) i miei problemi con le coppie surrogate sono finiti. Questa è davvero la soluzione migliore. – brannerchinese

3

È possibile ignorare la funzione Len in Python (vedi: How does len work?) e aggiungere un'istruzione if in esso per verificare la lunga unicode supplementare.

6

Faccio una funzione per fare questo su Python 2:

SURROGATE_PAIR = re.compile(u'[\ud800-\udbff][\udc00-\udfff]', re.UNICODE) 
def unicodeLen(s): 
    return len(SURROGATE_PAIR.sub('.', s)) 

Sostituendo le coppie di surrogati con un singolo carattere, abbiamo 'fix' la funzione len. Su stringhe normali, questo dovrebbe essere abbastanza efficiente: poiché il pattern non corrisponderà, la stringa originale verrà restituita senza modifiche. Dovrebbe funzionare anche su build Python larghe (32 bit), poiché la codifica della coppia surrogata non verrà utilizzata.

+0

Questo non funzionerà con i caratteri unicode a 4 byte, ad es. – wojcikstefan

+0

@wojcikstefan Dovrebbe farlo, perché lo dici? Il meccanismo di coppia surrogata codifica tutto ciò che non si adatta a UTF-16; è D83D DCAA, per esempio. –

+0

Mi aspetto un singolo carattere di bicep (come quello precedente) per restituire una lunghezza di '1', ma' unicodeLen (u '\ U0001f4aa \ U0001f3ff') 'restituisce' 2'. La mia aspettativa è errata @chrispy? – wojcikstefan