2012-06-20 10 views
7

Pracuję nad aplikacją, która używa tekstów z różnych języków, więc do przeglądania lub raportowania niektóre teksty (ciągi) muszą być posortowane w określonym języku.Sortowanie listy ciągów znaków z określonymi ustawieniami w pythonie

Obecnie mam obejście brudząc z globalnych ustawień regionalnych, co jest złe, i nie chcę, aby umieścić go w produkcji:

default_locale = locale.getlocale(locale.LC_COLLATE) 

def sort_strings(strings, locale_=None): 
    if locale_ is None: 
     return sorted(strings) 

    locale.setlocale(locale.LC_COLLATE, locale_) 
    sorted_strings = sorted(strings, cmp=locale.strcoll) 
    locale.setlocale(locale.LC_COLLATE, default_locale) 

    return sorted_strings 

Oficjalna dokumentacja locale pyton wyraźnie mówi, że zapisywanie i przywracanie jest to zły pomysł, ale nie daje żadnych sugestii: http://docs.python.org/library/locale.html#background-details-hints-tips-and-caveats

Odpowiedz

3

Glibc obsługuje API lokalizacji z wyraźnego stanu. Oto szybkie otoki dla tego API stworzone z ctypes.

# -*- coding: utf-8 
import ctypes 


class Locale(object): 
    def __init__(self, locale): 
     LC_ALL_MASK = 8127 
     # LC_COLLATE_MASK = 8 
     self.libc = ctypes.CDLL("libc.so.6") 
     self.ctx = self.libc.newlocale(LC_ALL_MASK, locale, 0) 



    def strxfrm(self, src, iteration=1): 
     size = 3 * iteration * len(src) 
     dest = ctypes.create_string_buffer('\000' * size) 
     n = self.libc.strxfrm_l(dest, src, size, self.ctx) 
     if n < size: 
      return dest.value 
     elif iteration<=4: 
      return self.strxfrm(src, iteration+1) 
     else: 
      raise Exception('max number of iterations trying to increase dest reached') 


    def __del__(self): 
     self.libc.freelocale(self.ctx) 

i krótki test

locale1 = Locale('C') 
locale2 = Locale('mk_MK.UTF-8') 

a_list = ['а', 'б', 'в', 'ј', 'ќ', 'џ', 'ш'] 
import random 
random.shuffle(a_list) 

assert sorted(a_list, key=locale1.strxfrm) == ['а', 'б', 'в', 'ш', 'ј', 'ќ', 'џ'] 
assert sorted(a_list, key=locale2.strxfrm) == ['а', 'б', 'в', 'ј', 'ќ', 'џ', 'ш'] 

co pozostało do zrobienia jest realizować wszystkie funkcje lokalizacji, wsparcie dla ciągów pyton Unicode (z WCHAR * Funkcje chyba) i automatycznie importować zawierać definicje plików lub coś

2

można użyć PyICU „s Collator aby uniknąć zmiany ustawień globalnych:

import icu # PyICU 

def sorted_strings(strings, locale=None): 
    if locale is None: 
     return sorted(strings) 
    collator = icu.Collator.createInstance(icu.Locale(locale)) 
    return sorted(strings, key=collator.getSortKey) 

przykład:

>>> L = [u'sandwiches', u'angel delight', u'custard', u'éclairs', u'glühwein'] 
>>> sorted_strings(L) 
['angel delight', 'custard', 'glühwein', 'sandwiches', 'éclairs'] 
>>> sorted_strings(L, 'en_US') 
['angel delight', 'custard', 'éclairs', 'glühwein', 'sandwiches'] 

Wada: zależność PyICU library; zachowanie jest nieco inne niż w przypadku locale.strcoll.


nie wiem jak dostać locale.strxfrm funkcję daną nazwę locale, nie zmieniając go na całym świecie. Jako włamać można uruchomić funkcję w innym procesie potomnym:

pool = multiprocessing.Pool() 
# ... 
pool.apply(locale_aware_sort, [strings, loc]) 

Wada: może być powolna, zasobożerne


Korzystanie zwykły threading.Lock nie będzie działać, chyba że możesz kontrolować każde miejsce, w którym funkcje świadome locale (nie są ograniczone do modułu locale np. re) mogą być wywoływane z wielu wątków.


Można skompilować funkcję używając Cython synchronizować dostęp za pomocą GIL. GIL upewni się, że żaden inny kod Pythona nie może zostać wykonany, gdy twoja funkcja jest uruchomiona.

Wada: nie czysty Python

2

rozwiązanie ctypes jest w porządku, ale jeśli ktoś w przyszłości chciałby po prostu zmodyfikować oryginalne rozwiązanie, tutaj jest sposób, jak to zrobić:

Tymczasowe zmiany ustawień globalnych można bezpiecznie wykonywać za pomocą menedżera kontekstów.

from contextlib import contextmanager 
import locale 

@contextmanager 
def changedlocale(newone): 
    old_locale = locale.getlocale(locale.LC_COLLATE) 
    try: 
     locale.setlocale(locale.LC_COLLATE, newone) 
     yield locale.strcoll 
    finally: 
     locale.setlocale(locale.LC_COLLATE, old_locale) 

def sort_strings(strings, locale_=None): 
    if locale_ is None: 
     return sorted(strings) 

    with changedlocale(locale_) as strcoll: 
     return sorted(strings, cmp=strcoll) 

    return sorted_strings 

Zapewnia to czyste przywrócenie pierwotnego ustawienia - o ile nie używasz gwintowania.

Powiązane problemy