2010-08-05 7 views
19

Ich habe ein Problem mit dem Sortieren von Listen mit Unicode-Kollatierung in Python 2.5.1 und 2.6.5 auf OSX, sowie auf Linux.Python sortiert Unicode nicht korrekt. Strcoll hilft nicht

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

Welche gedruckt werden soll:

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

Aber stattdessen druckt:

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

es zusammenfassend - es sieht aus, als ob strcoll gebrochen war. Versucht es mit verschiedenen Arten von Variablen (zB nicht-Unicode-codierten Strings).

Was mache ich falsch?

Mit freundlichen Grüßen, Tomasz Kopczuk.

+1

Was bedeutet 'locale.getlocale (LC_COLLATE)' Rückkehr nach Ihrer setlocaleQ Linie? – Amber

+0

Das Modul 'locale' verwendet die Locale-API aus der C-Bibliothek. Wenn ein Fehler auftritt, muss es sich daher in der C-Bibliothek befinden. Ein äquivalenter Test mit der Ländereinstellung 'de_DE.UTF-8' und der Zeichenfolge' ä' anstelle von '_' funktioniert ordnungsgemäß. Selbst wenn ich das deutsche Gebietsschema mit "±" verwende, ist die Reihenfolge korrekt, also muss etwas mit der polnischen Gebietsschema-Implementierung in der C-Bibliothek nicht stimmen. Als Workaround können Sie die Zeichenkette mit 'unicodedata.normalize' in Normalisierungsform D umwandeln, dann sollte sogar die naive' strcmp'-Ordnung funktionieren. – Philipp

+0

OK, das interessiert mich auch. Ich habe es mit 'pl_PL.UTF-8' und' de_DE.UTF-8' versucht und auch mit 'sort (key = locale.strxfrm)' anstelle von 'strcoll' auch auf OS X und im Moment bin ich dabei falsches Ergebnis Sting 'ä' mit de_DE.UTF8 hat bei mir nicht funktioniert. – chryss

Antwort

16

Offenbar die einzige Möglichkeit, auf allen Plattformen zu arbeiten, für die Sortierung ist die ICU-Bibliothek verwenden, um mit PyICU Bindungen (PyICU on PyPI).

Auf OS X: sudo port install py26-pyicu, Pflegebedürfnis beschrieben hier: https://svn.macports.org/ticket/23429 (oh die Freude an der Verwendung von Macports).

PyICUs Dokumentation ist leider sehr mangelt, aber ich schaffte es, um herauszufinden, wie es gemacht wird:

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

die gibt:

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

Ein weiteres Pro - @bobince: es ist Thread-sicher, so nicht nutzlos beim Festlegen von anforderungsorientierten Gebietsschemas.

+2

Gute Frage, und gute Antwort - und Sie sind voraus von allen durch ein paar Schritte, was kein Wunder ist, wenn Sie in Polen sind :). Wie auch immer, dies ist das zweite Mal, dass ich Probleme mit Python gesehen habe, wo es auf zugrundeliegenden C-Bibliotheken beruht. Weißt du, wo diese hergebracht werden könnten? – chryss

+0

Ich denke, es könnte ein Problem mit den Bibliotheken selbst sein, anstatt Python. Aber, wie gnibbler sagte - es funktioniert in einigen Betriebssystemen, so dass zumindest dieses spezielle Problem irgendwann behoben wurde. OS X ist berühmt für die Verwendung von alten GCC und so, und das andere Betriebssystem, das ich getestet habe, war Fedora 8 - was selbst nicht ganz zeitgemäß ist. Ich würde dies auf einer der Mailinglisten für die zugrunde liegenden C-Bibliotheken nachholen. Prost Mate :) –

+2

Ich stimme zu.Ich habe eine Gist http://gist.github.com/509520 erstellt und werde sie einigen Leuten zum Ausprobieren geben. Ich liebe * i18n, aber die Bugs machen es langweilig. – chryss

0

Auf ubuntu lucid scheint das Sortieren mit cmp in Ordnung zu sein, aber meine Ausgabecodierung ist falsch.

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

Mit der Taste mit locale.strxfrm funktioniert nicht, es sei denn ich bin etwas fehlt

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

Mit strxfrm Sie müssen die Unicode-Zeichenfolge AFAIK manuell dekodieren. –

+2

@tkopczuk, Es wäre schön, einen Weg zu finden, mit 'key' zu sortieren, da' cmp' für 'sorted' in Python3 verschwunden ist. –

+1

Es scheint mit der mitgelieferten Funktion functools.cmp_to_key zu funktionieren (' from functools import cmp_to_key '), so:' sortierte ([u'a ', u'z', u'ą '], Schlüssel = cmp_to_key (kollator.compare)) ' –

4

Nur zur Untersuchung von tkopczuk hinzufügen: Dies ist definitiv ein GCC-Bug, zumindest für Version 4.2.1 auf OS X 10.6.4. Es kann reproduziert werden, indem C strcoll() direkt als in this snippet aufgerufen wird.

EDIT: Immer noch auf dem gleichen System, finde ich, dass für die UTF-8-Versionen von de_DE, fr_FR, pl_PL, das Problem da ist, aber für die ISO-88591-Versionen von fr_FR und de_DE ist die Sortierreihenfolge korrekt. Leider für die OP, ISO-88592 pl_PL ist auch 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

Ist es möglich, '/ usr/share/locale/pl_PL.UTF-8/LC_COLLATE' in eine lesbare Form zu dekompilieren? Könnte kein gcc bug sein, aber falsche Kollatierungstabellen, wie @bobince gezeigt hat. –

+0

Nun, ich bekomme das gleiche Verhalten für Deutsch und Französisch (dh Zeichen mit diakritischen Zeichen sind nach "z" sortiert), also sind es nicht nur die polnischen Vergleichstabellen. Ich frage mich, ob es nicht nur C-Gebietsschema oder vielleicht das Standardgebietsschema wählt (meins ist en_GB - ist Ihr pl_PL?). Auf jeden Fall ist es eindeutig in der C-Bibliothek, ob in den Daten oder im Code kann ich nicht sagen. – chryss

+0

Yup, meins ist pl_PL. Aber es wäre nett, die Kollatierungstabellen zu überprüfen, und wenn sie koscher sind, gibt es das Problem mit verschiedenen Gebietsschemaeinstellungen, die von der Bibliothek verwendet werden. Aber ich denke, es ist die Bibliothek, daher die Probleme auf verschiedenen Betriebssystemen. –

4

@gnibbler, mit PyICU mit dem sortiert() Funktion funktioniert in einer Python3 Umwelt.Nach einer kleinen Graben durch die ICU API Dokumentation und einige Experimente kam ich über die getSortKey() Funktion:

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

die die gewünschte Kollation erzeugt:

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

anstelle der unerwünschten Kollation:

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

Hier ist, wie ich richtig persische Sprache sortieren verwaltet (ohne PyICU) (Python 3.x):

Zuerst das Gebietsschema festgelegt (nicht vergessen locale zu importieren und Plattform)

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) 

Dann Sortierschlüssel verwendet:

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

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

Für Liste der Objekte:

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

Schließlich können Sie das Gebietsschema zurück:

locale.setlocale(locale.LC_ALL, '') 
Verwandte Themen