2010-08-05 11 views
19

Ho un problema con gli elenchi di ordinamento utilizzando regole di confronto unicode in Python 2.5.1 e 2.6.5 su OSX, così come su Linux.Python non ordina correttamente unicode. Strcoll non aiuta

import locale 
locale.setlocale(locale.LC_ALL, 'pl_PL.UTF-8') 
print [i for i in sorted([u'a', u'z', u'ą'], cmp=locale.strcoll)] 

Che dovrebbe stampare:

[u'a', u'ą', u'z'] 

Ma invece ne emette:

[u'a', u'z', u'ą'] 

Riassumendo in su - è come se strcoll era rotto. Ho provato con vari tipi di variabili (es .: stringhe codificate non-unicode).

Cosa faccio di sbagliato?

Cordiali saluti, Tomasz Kopczuk.

+1

Cosa significa 'locale.getlocale (LC_COLLATE)' ritorno dopo la vostra linea setlocale? – Amber

+0

Il modulo 'locale' usa l'API locale dalla libreria C, quindi se c'è un errore deve essere nella libreria C. Un test equivalente con le impostazioni locali 'de_DE.UTF-8' e stringa' ä' invece di '' funziona correttamente. Anche se utilizzo le impostazioni locali tedesche con "±", l'ordine è corretto, quindi è necessario che ci sia qualcosa di sbagliato nell'implementazione della lingua polacca nella libreria C. Come soluzione alternativa, è possibile convertire la stringa in forma di normalizzazione D usando 'unicodedata.normalize', quindi anche l'ingenuo ordinamento' strcmp' dovrebbe funzionare. – Philipp

+0

OK, sono interessato anche a questo. Ho provato con 'pl_PL.UTF-8' e' de_DE.UTF-8', e anche con 'sort (chiave = locale.strxfrm)' invece di usare 'strcoll' anche su OS X e per il momento sto ottenendo il tuo risultato errato. Sting 'ä' con de_DE.UTF8 non ha funzionato per me. – chryss

risposta

16

A quanto pare, l'unico modo per l'ordinamento di lavorare su tutte le piattaforme è quello di utilizzare la libreria ICU con attacchi PyICU (PyICU on PyPI).

Su OS X: sudo port install py26-pyicu, bug di cura descritto qui: https://svn.macports.org/ticket/23429 (oh, la gioia di usare macports).

documentazione

PyICUs è purtroppo gravemente carente, ma sono riuscito a scoprire come è fatto:

import PyICU 
collator = PyICU.Collator.createInstance(PyICU.Locale('pl_PL.UTF-8')) 
print [i for i in sorted([u'a', u'z', u'ą'], cmp=collator.compare)] 

che dà:

[u'a', u'ą', u'z'] 

altro pro - @bobince: è thread-safe, in modo da non inutile quando si impostano le impostazioni locali richieste.

+2

Buona domanda e buona risposta - e sei avanti di tutti con pochi passi, il che non sorprende se sei in Polonia :). Comunque, questa è la seconda volta che ho visto problemi con Python dove si basa su librerie C sottostanti. Sai dove potrebbero essere allevati? – chryss

+0

Penso che potrebbe essere un problema con le librerie stesse, piuttosto che con Python. Ma come ha fatto notare gnibbler, succede che funziona in alcuni sistemi operativi, quindi forse, almeno questo particolare problema, è stato risolto a un certo punto. OS X è famoso per l'uso del vecchio gcc e così, e l'altro sistema operativo che ho testato era Fedora 8 - che di per sé non è abbastanza contemporaneo. Lo porterei su una delle mailing list per le librerie C sottostanti. Cheers amico :) –

+2

Sono d'accordo.Ho creato un Gist http://gist.github.com/509520 e lo darò a poche persone per provarlo. Io * amo * i18n, ma gli insetti lo rendono noioso. – chryss

0

Su ubuntu lucid l'ordinamento con cmp sembra funzionare correttamente, ma la codifica dell'output è errata.

>>> import locale 
>>> locale.setlocale(locale.LC_ALL, 'pl_PL.UTF-8') 
'pl_PL.UTF-8' 
>>> print [i for i in sorted([u'a', u'z', u'ą'], cmp=locale.strcoll)] 
[u'a', u'\u0105', u'z'] 

Utilizzando chiave con locale.strxfrm non funziona a meno che non mi manca qualcosa

>>> print [i for i in sorted([u'a', u'z', u'ą'], key=locale.strxfrm)] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
UnicodeEncodeError: 'ascii' codec can't encode character u'\u0105' in position 0: ordinal not in range(128) 
+0

Con strxfrm È necessario decodificare manualmente la stringa Unicode AFAIK. –

+2

@tkopczuk, Sarebbe bello trovare un modo per ordinare usando 'key' come' cmp' per 'sorted' è andato in Python3 –

+1

Sembra funzionare bene con la funzione functools.cmp_to_key fornita (' da functools import cmp_to_key '), così:' ordinato ([u'a ', u'z', u'ą '], chiave = cmp_to_key (collator.compare)) ' –

4

Solo per aggiungere all'indagine di tkopczuk: Questo è sicuramente un bug gcc, almeno per la versione 4.2.1 su OS X 10.6.4. Può essere riprodotto chiamando C strcoll() direttamente come in this snippet.

EDIT: Sempre sullo stesso sistema, trovo che per le versioni UTF-8 di de_DE, fr_FR, pl_PL, il problema ci sia, ma per le versioni ISO-88591 di fr_FR e de_DE, l'ordinamento è corretto. Purtroppo per l'OP, ISO-88.592 pl_PL è anche buggy:

The order for Polish ISO-8859 is: 
LATIN SMALL LETTER A 
LATIN SMALL LETTER Z 
LATIN SMALL LETTER A WITH OGONEK 
The LC_COLLATE culture and encoding settings were pl_PL, ISO8859-2. 

The order for Polish Unicode is: 
LATIN SMALL LETTER A 
LATIN SMALL LETTER Z 
LATIN SMALL LETTER A WITH OGONEK 
The LC_COLLATE culture and encoding settings were pl_PL, UTF8. 

The order for German Unicode is: 
LATIN SMALL LETTER A 
LATIN SMALL LETTER Z 
LATIN SMALL LETTER A WITH DIAERESIS 
The LC_COLLATE culture and encoding settings were de_DE, UTF8. 

The order for German ISO-8859 is: 
LATIN SMALL LETTER A 
LATIN SMALL LETTER A WITH DIAERESIS 
LATIN SMALL LETTER Z 
The LC_COLLATE culture and encoding settings were de_DE, ISO8859-1. 

The order for Fremch ISO-8859 is: 
LATIN SMALL LETTER A 
LATIN SMALL LETTER E WITH ACUTE 
LATIN SMALL LETTER Z 
The LC_COLLATE culture and encoding settings were fr_FR, ISO8859-1. 

The order for French Unicode is: 
LATIN SMALL LETTER A 
LATIN SMALL LETTER Z 
LATIN SMALL LETTER E WITH ACUTE 
The LC_COLLATE culture and encoding settings were fr_FR, UTF8. 
+0

È possibile decompilare '/ usr/share/locale/pl_PL.UTF-8/LC_COLLATE' in una forma leggibile? Potrebbe non essere un errore gcc dopo tutto, ma tabelle di confronto errate, come ha sottolineato @bobince. –

+0

Bene, ho lo stesso comportamento per tedesco e francese (cioè, i caratteri con segni diacritici sono ordinati dopo "z"), quindi non sono solo le tabelle di confronto polacchi. Mi chiedo se non seleziona solo le impostazioni locali di C o le impostazioni internazionali predefinite (il mio è en_GB - è il tuo pl_PL?). In ogni caso, è chiaramente nella libreria C, sia nei dati che nel codice che non posso dire. – chryss

+0

Sì, il mio è pl_PL. Ma sarebbe bello controllare le tabelle di confronto e se sono kosher, allora c'è il problema con le diverse impostazioni locali utilizzate dalla libreria. Ma immagino sia la libreria, quindi i problemi su vari sistemi operativi. –

4

@gnibbler, utilizzando PyICU con il ordinato() funzione funziona in un ambiente python3.Dopo un po 'di scavo attraverso la documentazione ICU API e alcuni esperimenti, ho trovato la funzione getSortKey():

import PyICU 
collator = PyICU.Collator.createInstance(PyICU.Locale('de_DE.UTF-8')) 
sorted(['a','b','c','ä'],key=collator.getSortKey) 

che produce la fascicolazione desiderata:

['a', 'ä', 'b', 'c'] 

anziché la collazione indesiderato:

sorted(['a','b','c','ä']) 
['a', 'b', 'c', 'ä'] 
2

Ecco come sono riuscito a ordinare lingua persiana correttamente (senza PyICU) (usando python 3.x):

Prima impostare il locale (non dimenticate di importare locale e piattaforma)

if platform.system() == 'Linux': 
    locale.setlocale(locale.LC_ALL, 'fa_IR.UTF-8') 
elif platform.system() == 'Windows': 
    locale.setlocale(locale.LC_ALL, 'Persian_Iran.1256') 
else: 
    pass (or any other OS) 

quindi ordinare con il tasto:

a = ['ا','ب','پ','ت','ث','ج','چ','ح','خ','د','ذ','ر','ز','ژ','س','ش','ص','ض','ط','ظ','ع','غ','ف','ق','ک','گ','ل','م','ن','و','ه','ي'] 

print(sorted(a,key=locale.strxfrm)) 

Per la lista di oggetti:

a = [{'id':"ا"},{'id':"ب"},{'id':"پ"},{'id':"ت"},{'id':"ث"},{'id':"ج"},{'id':"چ"},{'id':"ح"},{'id':"خ"},{'id':"د"},{'id':"ذ"},{'id':"ر"},{'id':"ز"},{'id':"ژ"},{'id':"س"},{'id':"ش"},{'id':"ص"},{'id':"ض"},{'id':"ط"},{'id':"ظ"},{'id':"ع"},{'id':"غ"},{'id':"ف"},{'id':"ق"},{'id':"ک"},{'id':"گ"},{'id':"ل"},{'id':"م"},{'id':"ن"},{'id':"و"},{'id':"ه"},{'id':"ي"}] 

print(sorted(a, key=lambda x: locale.strxfrm(x['id'])) 

Infine è possibile restituire il locale:

locale.setlocale(locale.LC_ALL, '') 
Problemi correlati