2010-08-05 5 views
19

J'ai un problème avec le tri des listes en utilisant un classement Unicode sous Python 2.5.1 et 2.6.5 sous OSX, ainsi que sous Linux.Python ne triant pas unicode correctement. Strcoll n'aide pas

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)] 

qui devrait imprimer:

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

Mais au lieu imprime sur:

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

Résumant it up - il semble que strcoll était cassé. Je l'ai essayé avec différents types de variables (par exemple, des chaînes codées non-unicode).

Qu'est-ce que je fais de mal? Cordialement, Tomasz Kopczuk.

Meilleures salutations, Tomasz Kopczuk.

+1

Qu'est-ce que le retour 'de locale.getlocale (LC_COLLATE)' après votre ligne setlocale? – Amber

+0

Le module 'locale' utilise l'API locale de la bibliothèque C, donc s'il y a une erreur il doit être dans la bibliothèque C. Un test équivalent avec les paramètres régionaux 'de_DE.UTF-8' et la chaîne' ä' au lieu de '± fonctionne correctement. Même si j'utilise les paramètres régionaux en allemand avec ''', l'ordre est correct, donc il doit y avoir quelque chose qui ne va pas avec l'implémentation de la locale en polonais dans la bibliothèque C. Comme une solution de contournement vous pouvez convertir la chaîne à la forme de normalisation D en utilisant 'unicodedata.normalize', alors même l'ordre naïf' strcmp' devrait fonctionner. – Philipp

+0

OK, ça m'intéresse aussi. Je l'ai essayé avec 'pl_PL.UTF-8' et' de_DE.UTF-8', et aussi avec 'sort (key = locale.strxfrm)' au lieu d'utiliser 'strcoll' aussi sur OS X et pour le moment j'obtiens votre résultat incorrect. Sting 'ä' avec de_DE.UTF8 n'a pas fonctionné pour moi. – chryss

Répondre

16

Apparemment, la seule façon pour le tri de travailler sur toutes les plates-formes est d'utiliser la bibliothèque de soins intensifs avec des liaisons PyICU (PyICU on PyPI).

Sur OS X: sudo port install py26-pyicu, bug d'avertissement décrit ici: https://svn.macports.org/ticket/23429 (oh la joie d'utiliser macports).

documentation PyICUs est malheureusement cruellement défaut, mais je réussi à savoir comment faire:

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)] 

qui donne:

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

Une autre pro - @bobince: il est thread-safe, donc pas inutile lors de la définition des paramètres régionaux par requête.

+2

Bonne question, et bonne réponse - et vous êtes en avance de tout le monde par quelques étapes, ce qui n'est pas étonnant si vous êtes en Pologne :). Quoi qu'il en soit, c'est la deuxième fois que j'ai vu des problèmes avec Python où il repose sur des bibliothèques C sous-jacentes. Savez-vous où ceux-ci pourraient être élevés? – chryss

+0

Je pense que cela pourrait être un problème avec les bibliothèques elles-mêmes, plutôt que Python. Mais, comme l'a souligné M. Gnibbler, cela fonctionne dans certains systèmes d'exploitation, alors peut-être, du moins ce problème particulier, a-t-il été réglé à un moment donné. OS X est célèbre pour l'utilisation de vieux gcc et ainsi, et l'autre OS que j'ai testé était Fedora 8 - qui n'est pas tout à fait contemporain. Je voudrais apporter cela à l'une des listes de diffusion pour les bibliothèques C sous-jacentes. Salutations mec :) –

+2

Je suis d'accord.J'ai fait un Gist http://gist.github.com/509520 et je vais le donner à quelques personnes pour essayer. Je * aime * i18n, mais les bugs le rendent fastidieux. – chryss

0

Sur Ubuntu lucid, le tri avec cmp semble fonctionner, mais mon encodage de sortie est faux.

>>> 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'] 

avec l'aide de la clé locale.strxfrm ne fonctionne pas à moins que je manque quelque chose

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

Avec strxfrm Vous devez décoder manuellement la chaîne unicode AFAIK. –

+2

@tkopczuk, Il serait bien de trouver un moyen de tri en utilisant 'key' car' cmp' pour 'trié' est parti dans Python3 –

+1

Il semble fonctionner correctement avec la fonction functools.cmp_to_key fournie (' from functools import cmp_to_key '), comme ça:' trié ([u'a ', u'z', u'ą '], clé = cmp_to_key (collator.compare)) ' –

4

Juste pour ajouter à l'enquête de tkopczuk: C'est certainement un bug de gcc, au moins pour la version 4.2.1 sur OS X 10.6.4. Il peut être reproduit en appelant C strcoll() directement comme in this snippet. Toujours sur le même système, je trouve que pour les versions UTF-8 de de_DE, fr_FR, pl_PL, le problème est là, mais pour les versions ISO-88591 de fr_FR et de_DE, l'ordre de tri est correct. Malheureusement pour l'OP, ISO-88592 pl_PL est bogué:

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

Est-il possible de décompiler '/ usr/share/locale/pl_PL.UTF-8/LC_COLLATE' en une sorte de forme lisible? Peut-être pas un bug gcc après tout, mais de mauvaises tables de classement, comme l'a souligné @bobince. –

+0

Eh bien, je reçois le même comportement pour l'allemand et le français (ie, les caractères avec des signes diacritiques sont triés après "z"), donc ce ne sont pas seulement les tables de classement polonaises. Je me demande s'il ne choisit pas seulement les paramètres régionaux de C ou peut-être les paramètres régionaux par défaut (le mien est en_GB - est vôtre pl_PL?). En tout cas, c'est clairement dans la bibliothèque C, que ce soit dans les données ou dans le code que je ne peux pas dire. – chryss

+0

Yup, le mien est pl_PL. Mais il serait bien de vérifier les tables de classement et si elles sont casher, alors il y a le problème avec différents paramètres régionaux utilisés par la bibliothèque. Mais je suppose que c'est la bibliothèque, d'où les problèmes sur différents systèmes d'exploitation. –

4

@gnibbler, en utilisant PyICU avec le trié() fonction fonctionne dans un environnement python3.Après un peu de creuser à travers la documentation de l'API de soins intensifs et une expérimentation, je suis tombé sur le getSortKey() fonction:

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

qui produit la collecte souhaitée:

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

au lieu de la collecte indésirable:

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

Voici comment je réussi à trier correctement la langue persane (sans PyICU) (en utilisant 3.x python):

d'abord définir les paramètres régionaux (ne pas oublier d'importer locale et plate-forme)

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) 

Puis trier par clé:

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

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

Pour la liste des objets:

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'])) 

vous pouvez enfin retourner les paramètres régionaux:

locale.setlocale(locale.LC_ALL, '')