2015-11-01 16 views
8

Si verifica un comportamento strano quando si utilizza la libreria locale con input Unicode. Di seguito è riportato un esempio di lavoro minimo:Caratteri Unicode non compresi nell'intervallo quando si chiama locale.strxfrm

>>> x = '\U0010fefd' 
>>> ord(x) 
1113853 
>>> ord('\U0010fefd') == 0X10fefd 
True 
>>> ord(x) <= 0X10ffff 
True 
>>> import locale 
>>> locale.strxfrm(x) 
'\U0010fefd' 
>>> locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') 
'en_US.UTF-8' 
>>> locale.strxfrm(x) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
ValueError: character U+110000 is not in range [U+0000; U+10ffff] 

Ho visto questo su Python 3.3, 3.4 e 3.5. Non ricevo un errore su Python 2.7.

Per quanto posso vedere, il mio ingresso Unicode è all'interno della gamma unicode appropriata, così sembra che in qualche modo qualcosa di interno al strxfrm quando si utilizza il 'en_US.UTF-8' si sta muovendo l'ingresso fuori portata.

Sto eseguendo Mac OS X e questo comportamento potrebbe essere correlato a http://bugs.python.org/issue23195 ... ma avevo l'impressione che questo errore si manifestasse solo come risultati errati, non come un'eccezione sollevata. Non riesco a replicare sulla mia macchina SLES 11, altri confermano che non possono replicarsi su Ubuntu, Centos o Windows. Potrebbe essere istruttivo ascoltare altri OS nei commenti.

Qualcuno può spiegare cosa potrebbe accadere qui sotto il cofano?

+0

Non riesco a riprodurlo su Ubuntu. 'locale.strxfrm (x)' restituisce ''\ x01 \ x01 \ x01 \ x01 Ւ'' nella locale' en_US.UTF-8'. – jfs

+1

potresti usare ['icu.Collator.createInstance (icu.Locale ('en_US')). GetSortKey' invece] (http://stackoverflow.com/a/32178778/4279) – jfs

+0

@JFSebastian Sì, ho usato PyICU e conferma che non ci sono problemi lì. Ero più preoccupato per questo comportamento nel modulo 'locale' di stdlib e se si trattava di una sorta di errore dell'utente (cioè ho fatto qualcosa di sbagliato) o se c'è qualcosa di più nefasto in corso. – SethMMorton

risposta

7

In Python 3.x, la funzione locale.strxfrm(s) utilizza internamente la funzione POSIX C wcsxfrm(), che si basa sull'impostazione corrente di LC_COLLATE. Lo standard POSIX definisce la trasformazione in questo modo:

The transformation shall be such that if wcscmp() is applied to two transformed wide strings, it shall return a value greater than, equal to, or less than 0, corresponding to the result of wcscoll() applied to the same two original wide-character strings.

Questa definizione può essere implementato in diversi modi, e non richiede anche che la stringa risultante è leggibile.

ho creato un piccolo esempio di codice C per dimostrare come funziona:

#include <stdio.h> 
#include <wchar.h> 
#include <locale.h> 

int main() { 
    wchar_t buf[10]; 
    wchar_t *in = L"\x10fefd"; 
    int i; 

    setlocale(LC_COLLATE, "en_US.UTF-8"); 

    printf("in : "); 
    for(i=0;i<10 && in[i];i++) 
    printf(" 0x%x", in[i]); 
    printf("\n"); 

    i = wcsxfrm(buf, in, 10); 

    printf("out: "); 
    for(i=0;i<10 && buf[i];i++) 
    printf(" 0x%x", buf[i]); 
    printf("\n"); 
} 

esso stampa la stringa prima e dopo la trasformazione.

in esecuzione su Linux (Debian Jessie) questo è il risultato:

in : 0x10fefd 
out: 0x1 0x1 0x1 0x1 0x552 

durante l'esecuzione su OSX (10.11.1) il risultato è:

in : 0x10fefd 
out: 0x103 0x1 0x110000 

Si può vedere che la l'output di wcsxfrm() su OSX contiene il carattere U + 110000 che non è consentito in una stringa Python, quindi questa è la fonte dell'errore.

Su Python 2.7 l'errore non viene generato perché la sua implementazione locale.strxfrm() si basa sulla funzione strxfrm()C.

UPDATE:

Indagare ulteriormente, vedo che la definizione LC_COLLATE per en_US.UTF-8 su OSX è un collegamento alla definizione la_LN.US-ASCII.

$ ls -l /usr/share/locale/en_US.UTF-8/LC_COLLATE 
lrwxr-xr-x 1 root wheel 28 Oct 1 14:24 /usr/share/locale/en_US.UTF-8/LC_COLLATE -> ../la_LN.US-ASCII/LC_COLLATE 

ho trovato la definizione effettiva nel sources da Apple.Il contenuto del file di la_LN.US-ASCII.src è la seguente:

order \ 
    \x00;...;\xff 

2 ° UPDATE:

Ho testato ulteriormente la funzione wcsxfrm() su OSX. Utilizzando la fascicolazione la_LN.US-ASCII, data una sequenza di carattere esteso C1..Cn come ingresso, l'uscita è una stringa con questa forma:

W1..Wn \x01 U1..Un 

dove

Wx = 0x103 if Cx > 0xFF else Cx+0x3 
Ux = Cx+0x103 if Cx > 0xFF else Cx+0x3 

Utilizzando questo algoritmo \x10fefd diventano 0x103 0x1 0x110000

Ho controllato e tutte le impostazioni internazionali UTF-8 utilizzano questo fascicolo su OSX, quindi sono incline a dire che il supporto di fascicolazione per UTF-8 su sistemi Apple è rotto. L'ordinamento risultante è quasi lo stesso di quello ottenuto con il confronto dei byte normali, con il vantaggio della possibilità di ottenere caratteri Unicode illegali.

+0

Huh. Quindi sembra che non ci sia molto che posso fare per prevenire il 'ValueError' dal momento che questo proviene dalla libreria C sottostante, al di fuori del controllo di Python. – SethMMorton

+0

Mi chiedo se questo sarebbe considerato un bug. Supponendo che '0x110000' sia un valore di ritorno valido per' wcsxfrm() 'allora Python dovrebbe essere in grado di gestirlo internamente, correggere? Tuttavia, se '0x110000' non è valido, suppongo che ciò che sta facendo Python sia" corretto ". – SethMMorton

+0

Sembra che questo sia venuto fuori 4 anni fa: https://mail.python.org/pipermail/python-dev/2011-December/114759.html e http://bugs.python.org/issue13441. Dai miei occhi non sembra che abbiano trovato una soluzione agli errori per i valori> = '0x110000', ma il consenso era che sicuramente non li volevano. – SethMMorton

Problemi correlati