Сортировка списка строк с определенной локалью в 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
3 ответа
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 *) и автоматически импортировать определения включаемых файлов или что-то в этом роде.
Вы можете использовать коллатер 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
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
Это гарантирует чистое восстановление исходного языкового стандарта - до тех пор, пока вы не используете многопоточность.