Сортировка списка строк с определенной локалью в python
Я работаю над приложением, которое использует тексты с разных языков, поэтому для просмотра или создания отчетов некоторые тексты (строки) нужно сортировать на определенном языке.
В настоящее время у меня есть обходное взаимодействие с глобальными настройками локали, что плохо, и я не хочу его выпускать:
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
Официальная документация на языке python явно говорит о том, что сохранение и восстановление - плохая идея, но не дает никаких предложений: http://docs.python.org/library/locale.html#background-details-hints-tips-and-caveats
Ответы
Ответ 1
Glibc поддерживает API локали с явным состоянием. Вот быстрая оболочка для этого API, сделанная с помощью 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)
и короткий тест
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) == ['а', 'б', 'в', 'ј', 'ќ', 'џ', 'ш']
что нужно сделать, это реализовать все функции языка, поддерживать строки юникода python (с функциями wchar *, которые, как я полагаю), и автоматически импортировать описания файлов include или что-то
Ответ 2
Вы можете использовать PyICU, чтобы избежать изменения глобальных настроек:
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)
Пример:
>>> 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']
Недостаток: зависимость от библиотека PyICU; поведение немного отличается от locale.strcoll
.
Я не знаю, как получить функцию locale.strxfrm
, учитывая имя локали, не изменяя ее глобально. В качестве взлома вы можете запустить свою функцию в другом дочернем процессе:
pool = multiprocessing.Pool()
# ...
pool.apply(locale_aware_sort, [strings, loc])
Недостаток: может быть медленным, ресурсный голод
Использование обычного threading.Lock
не будет работать, если вы не сможете управлять каждым местом, где функции локального распознавания (они не ограничены модулем locale
, например, re
) могут быть вызваны из нескольких потоков.
Вы можете скомпилировать свою функцию с помощью Cython, чтобы синхронизировать доступ с помощью GIL. GIL будет убедиться, что никакой другой код Python не будет выполнен во время работы вашей функции.
Недостаток: не чистый Python
Ответ 3
Решение ctypes
прекрасное, но если кто-то в будущем захочет просто изменить ваше исходное решение, вот как это сделать:
Временные изменения глобальных параметров можно безопасно выполнить с помощью менеджера контекста.
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
Это обеспечивает чистое восстановление исходного языка - если вы не используете потоки.