2009-09-08 16 views
6

Voglio ordinare una lista di dizionari per chiave di dizionario, dove non voglio distinguere tra caratteri maiuscoli e minuscoli.python: combina sort-key-functions itemgetter e str.lower

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] 

dovrebbe fornire

Ali, Hans, klaus, Krishna, Paul, peter 

e non

klaus, peter, Ali, Hans, Krishna, Paul 

risposta

10

Nel caso generale, è Voglio scrivere la tua funzione di estrazione della chiave per scopi di smistamento; solo in casi speciali (sebbene importanti) succede che puoi semplicemente riutilizzare un callable esistente per estrarre le chiavi per te, o semplicemente unire un paio di quelle esistenti (in modo "veloce e sporco" usando lambda, poiché non c'è in modo da fare la composizione della funzione).

Se spesso necessario eseguire questi due tipi di operazioni per l'estrazione chiave (ottenere un elemento e chiamare un metodo su tale elemento), suggerisco:

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

così listofdicts.sort(key=combiner('name', 'lower')) funzionerà nel tuo caso.

nota che mentre eccessiva generalizzazione ha costi, generalizzazione gusto e moderata (lasciando la chiave articolo, nome del metodo e metodo argomenti eventuali, come runtime-determinata, in questo caso) contiene generalmente benefici - una funzione generale, non più complesso di una dozzina di specifici e specializzati (con l'estrattore, il metodo per chiamare, o entrambi, cablati nel loro codice), sarà più facile da mantenere (e, ovviamente, molto più facile da riutilizzare! -).

2
def lower_getter(field): 
    def _getter(obj): 
     return obj[field].lower() 
    return _getter 

list_of_dicts.sort(key=lower_getter(key_field)) 
+0

e come un plus, funzionerà automaticamente con entrambe le stringhe di byte e stringhe Unicode. – nosklo

12

ne dite di questo:

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

ottengo un errore "Non iterare" – chovy

4

Probabilmente si dovrebbe andare con un lambda per motivi di leggibilità. Ma come interessante studio sulle funzioni di ordine superiore, ecco la versione estesa di q-combinator in Python (noto anche come il combinatore di uccelli queer). Questo consente di creare una nuova funzione componendo due funzioni

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'})) 

Se si inverte le definizioni di interno ed esterno nella funzione compose, si ottiene il più tradizionale b-Combinator (bluebird). Mi piace il q-combinatore più a causa della somiglianza con i tubi unix.

4

Questa soluzione utilizzerà le impostazioni locali del sistema e, come bonus, ordinerà eventuali altri caratteri in base alle impostazioni locali correnti (inserirà "ü" dopo "u" in una lingua tedesca ecc.).

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"))) 

Questo naturalmente utilizza che il locale tipo è l'interfaccia utente tipo "naturale" che si desidera emulare con .lower().

Sono stupito che il modulo di Python locale è sconosciuto e inutilizzato, di sicuro è una componente importante nell'applicazione scrivo (tradotto in più lingue, ma il modulo locale è importante per ottenere anche uno modulo a destra. Caso in questione: nello svedese 'V' e 'W' uguagliano allo stesso modo, quindi devi fascicolarli. locale fa tutto questo per te.). Nella locale POSIX (non predefinita), questo ritornerà all'ordinamento "a" dopo "Z".

+0

Questo è un bel suggerimento, basta cambiare keyfunc a: def keyfunc (DIC): ritorno strxfrm (DIC [ "nome"]) – Francesco

+0

Francesco: Ora si utilizza una fabbrica più personalizzabile stile (anche se può essere specializzato per essere più veloce, raramente è importante). – u0b34a0f6ae

4

Personalmente, vorrei che ci fossero due funzioni della libreria standard di Python (probabilmente nel functools):

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 

ho implementato questi per mio uso in jaraco.util.functools.

In entrambi i casi, ora il codice è abbastanza chiaro, autodocumentato e robusto (IMO).

lower = method_caller('lower') 
get_name = itemgetter('name') 
lowered_name = compose(lower, get_name) 

list_of_dicts.sort(key=lowered_name) 
3
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))