2012-06-20 14 views
7

Trabajo en una aplicación que usa textos de diferentes idiomas, por lo tanto, para ver o informar, algunos textos (cadenas) deben ordenarse en un idioma específico.Ordenando la lista de cadenas con configuración regional específica en python

Actualmente tengo una solución de jugar con la configuración regional a nivel mundial, lo que es malo, y no quiero ponerlo en producción:

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 

La documentación oficial local pitón dice explícitamente que guardar y restaurar es una mala idea, pero no da ninguna sugerencia: http://docs.python.org/library/locale.html#background-details-hints-tips-and-caveats

Respuesta

3

Glibc es compatible con una API local con un estado explícito. Aquí hay un contenedor rápido para esa API hecha con 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) 

y una prueba corta

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) == ['а', 'б', 'в', 'ј', 'ќ', 'џ', 'ш'] 

lo que queda por hacer es poner en práctica todas las funciones de localización, el apoyo a cadenas Unicode de Python (con wchar * funciones supongo), e importar automáticamente la incluyen definiciones de archivo o algo

2

se puede usar un alzadora PyICU 's para evitar cambiar la configuración global:

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) 

Ejemplo:

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

Desventaja: dependencia de PyICU library; el comportamiento es ligeramente diferente de locale.strcoll.


No sé cómo conseguir locale.strxfrm función dado un nombre de entorno sin cambiar de forma global. Como hackear se podía ejecutar su función en un proceso hijo distinto:

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

Desventaja: podría ser lento, ávidas de recursos


Usando ordinaria threading.Lock no funcionará a menos puede controlar todos los lugares donde las funciones de reconocimiento local (no están limitadas al módulo locale, por ejemplo, re) pueden ser llamadas desde múltiples hilos.


Usted puede compilar su función utilizando Cython para sincronizar el acceso utilizando GIL. GIL se asegurará de que no se pueda ejecutar ningún otro código de Python mientras se ejecuta su función.

Desventaja: no Python puro

2

la solución ctypes está bien, pero si alguien en el futuro le gustaría acaba de modificar su solución original, aquí es una manera de cómo hacerlo:

Los cambios temporales de la configuración global se pueden realizar de manera segura con un administrador de contexto.

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 

Esto garantiza una restauración limpia de la configuración original, siempre que no utilice el enhebrado.

Cuestiones relacionadas