python: combine el itemgetter de las funciones de ordenación de teclas y str.lower

Quiero ordenar una lista de diccionarios por clave de diccionario, donde no quiero distinguir entre mayúsculas y minúsculas.

dict1 = {'name':'peter','phone':'12355'} dict2 = {'name':'Paul','phone':'545435'} dict3 = {'name':'klaus','phone':'55345'} dict4 = {'name':'Krishna','phone':'12345'} dict5 = {'name':'Ali','phone':'53453'} dict6 = {'name':'Hans','phone':'765756'} list_of_dicts = [dict1,dict2,dict3,dict4,dict5,dict6] key_field = 'name' list_of_dicts.sort(key=itemgetter(key_field)) # how to combine key=itemgetter(key_field) and key=str.lower? for list_field in list_of_dicts: print list_field[key_field] 

deberia proporcionar

 Ali, Hans, klaus, Krishna, Paul, peter 

y no

 klaus, peter, Ali, Hans, Krishna, Paul 

En el caso general, querrá escribir su función de extracción de clave para propósitos de clasificación; solo en casos especiales (aunque importantes) puede ocurrir que solo pueda reutilizar una clave existente para extraer las claves por usted, o simplemente unir algunas de las existentes (de forma “rápida y sucia” usando lambda , ya que no hay ninguna en forma de hacer la composición de la función).

Si a menudo necesita realizar estos dos tipos de operaciones para la extracción de claves (obtener un elemento y llamar a un método en ese elemento), sugiero:

 def combiner(itemkey, methodname, *a, **k): def keyextractor(container): item = container[itemkey] method = getattr(item, methodname) return method(*a, **k) return keyextractor 

así que listofdicts.sort(key=combiner('name', 'lower')) funcionará en su caso.

Tenga en cuenta que si bien la generalización excesiva tiene costos, la generalización de buen gusto y moderada (dejar la clave del elemento, el nombre del método y los argumentos del método, si los hay, según lo determine el tiempo de ejecución, en este caso) generalmente tiene beneficios: una función general, no más compleja que una una docena de específicos y especializados (con el extractor, el método de llamada o ambos, incluidos en su código) serán más fáciles de mantener (y, por supuesto, mucho más fáciles de reutilizar! -).

Qué tal esto:

 list_of_dicts.sort(key=lambda a: a['name'].lower()) 

Probablemente debería ir con un lambda en aras de la legibilidad. Pero como un interesante estudio sobre funciones de orden superior, aquí está la versión extendida de q-combinator en Python (también conocido como el queer bird combinator). Esto le permite crear una nueva función al componer dos funciones

  def compose(inner_func, *outer_funcs): if not outer_funcs: return inner_func outer_func = compose(*outer_funcs) return lambda *args, **kwargs: outer_func(inner_func(*args, **kwargs)) from operator import itemgetter, methodcaller name_lowered = compose(itemgetter('name'), methodcaller('lower')) print(name_lowered( {'name': 'Foo'} )) 

Si invierte las definiciones de interior y exterior en la función de compose , obtendrá el combinador b más tradicional (bluebird). Me gusta el q-combinator más debido a la similitud con las tuberías de Unix.

Esta solución utilizará la configuración regional del sistema y, como ventaja adicional, también ordenará otros caracteres eventuales según la configuración regional actual (colocará “ü” después de “u” en una configuración regional alemana, etc.).

 from locale import setlocale, strxfrm, LC_ALL import operator # call setlocale to init current locale setlocale(LC_ALL, "") def locale_keyfunc(keyfunc): def locale_wrapper(obj): return strxfrm(keyfunc(obj)) return locale_wrapper list_of_dicts.sort(key=locale_keyfunc(operator.itemgetter("name"))) 

Esto, por supuesto, utiliza que la ordenación regional es la ordenación “natural” de la interfaz de usuario que desea emular con .lower ().

Me sorprende que el módulo de locale de python sea desconocido y no se use, es seguro que es un componente importante en la aplicación que escribo (traducido a múltiples idiomas, pero el módulo de configuración regional es importante incluso para obtener un módulo correcto. Caso en cuestión: en sueco ‘V’ y ‘W’ se ordenan por igual, así que tienes que compaginarlos. locale hace todo eso por ti. En la configuración regional POSIX (no predeterminada), esto volverá a ordenar “a” después de “Z”.

Personalmente, me gustaría que hubiera dos funciones en la biblioteca estándar de Python (probablemente en funciones):

 def compose(*funcs): """ Compose any number of unary functions into a single unary function. >>> import textwrap >>> str.strip(textwrap.dedent(compose.__doc__)) == compose(str.strip, textwrap.dedent)(compose.__doc__) True """ compose_two = lambda f1, f2: lambda v: f1(f2(v)) return reduce(compose_two, funcs) def method_caller(method_name, *args, **kwargs): """ Return a function that will call a named method on the target object with optional positional and keyword arguments. >>> lower = method_caller('lower') >>> lower('MyString') 'mystring' """ def call_method(target): func = getattr(target, method_name) return func(*args, **kwargs) return call_method 

He implementado estos para mi propio uso en jaraco.util.functools .

De cualquier manera, ahora su código es bastante claro, auto documentado y robusto (IMO).

 lower = method_caller('lower') get_name = itemgetter('name') lowered_name = compose(lower, get_name) list_of_dicts.sort(key=lowered_name) 
 from functools import partial def nested_funcs(*funcs): return partial(reduce, lambda arg, func: func(arg), funcs) sorted(list_of_dicts, key=nested_funcs(itemgetter('name'), str.strip, str.lower)) 
 def lower_getter(field): def _getter(obj): return obj[field].lower() return _getter list_of_dicts.sort(key=lower_getter(key_field))