Mappatura su valori in un dizionario Python


243

Dato un dizionario { k1: v1, k2: v2 ... }che desidero ricevere, { k1: f(v1), k2: f(v2) ... }passo una funzione f.

Esiste una tale funzione integrata? O devo farlo

dict([(k, f(v)) for (k, v) in my_dictionary.iteritems()])

Idealmente vorrei solo scrivere

my_dictionary.map_values(f)

o

my_dictionary.mutate_values_with(f)

Cioè, non mi importa se il dizionario originale è mutato o se ne viene creata una copia.


2
Un modo migliore di scrivere il tuo esempio sarebbe dict((k, f(v)) for k, v in mydict.iteritems()), cioè senza le parentesi quadre, che impedirebbe la creazione di un elenco intermedio tramite un generatore.
Bereal

Risposte:


355

Non esiste una tale funzione; il modo più semplice per farlo è usare una comprensione dettata:

my_dictionary = {k: f(v) for k, v in my_dictionary.items()}

In Python 2.7, utilizzare il .iteritems()metodo anziché .items()per risparmiare memoria. La sintassi di comprensione del dict non è stata introdotta fino a Python 2.7.

Si noti che non esiste nemmeno un metodo simile negli elenchi; dovresti usare un elenco di comprensione o la map()funzione.

Pertanto, puoi utilizzare la map()funzione anche per elaborare il tuo dict:

my_dictionary = dict(map(lambda kv: (kv[0], f(kv[1])), my_dictionary.iteritems()))

ma non è così leggibile, davvero.


5
+1: questo è quello che vorrei fare anch'io. dict(zip(a, map(f, a.values())))è leggermente più breve, ma devo pensare a cosa sta facendo e ricordare a me stesso che chiavi e valori vengono ripetuti nello stesso ordine se il dict non cambia. Non devo assolutamente pensare a cosa sta facendo il dictcomp, quindi è la risposta giusta.
DSM

2
@chiborg: questo perché piuttosto che cercare tutte le coppie chiave-valore in una volta, ora stai usando il numero di chiavi volte le my_dictionary.__getitem__chiamate.
Martijn Pieters

1
Si noti che dal momento che PEP3113 (implementato in Python 3.x) i parametri di tupla non sono più supportati: lambda (k,v): (k, f(v))devono essere riscritti in qualcosa del generelambda k_v: (k_v[0], f(k_v[1]))
Normanius

1
Perché l'estrazione dei parametri è stata annullata? Come è un miglioramento ?
javadba,

3
proveniente da un linguaggio FP, Python sembrerebbe incredibilmente imbarazzante.
juanchito


22

Puoi farlo sul posto, piuttosto che creare un nuovo dict, che può essere preferibile per dizionari di grandi dimensioni (se non hai bisogno di una copia).

def mutate_dict(f,d):
    for k, v in d.iteritems():
        d[k] = f(v)

my_dictionary = {'a':1, 'b':2}
mutate_dict(lambda x: x+1, my_dictionary)

risulta nel my_dictionarycontenere:

{'a': 2, 'b': 3}

1
Fresco, si dovrebbe forse cambiare titolo mapdictal mutate_values_witho qualcosa per rendere più chiaro che si riscrive la dict! :)
Tarrasch,

2
zip(d.keys(), d.values())funziona per più versioni invece diiteritems()
ytpillai

1
@ytpillai 'zip' o comprensioni ne fanno una copia, piuttosto che cambiare i valori sul posto, che è lo scopo della mia risposta. La risposta accettata è la migliore per quando una copia è ok.
gens

1
Mi scuso, non avevo capito che volevi usare il metodo items. Tuttavia, è possibile anche un altro miglioramento (per utenti non Python 2.7){k:f(v) for k,v in iter(d.items())}
ytpillai

1
Risparmia spazio creando un iteratore
ytpillai

13

A causa di PEP-0469 che ha ribattezzato iteritems () in items () e PEP-3113 che hanno rimosso la decompressione dei parametri Tuple , in Python 3.x dovresti scrivere Martijn Pieters ♦ rispondi in questo modo:

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))

4

Mentre la mia risposta originale ha mancato il punto (cercando di risolvere questo problema con la soluzione di accesso alla chiave in fabbrica di defaultdict ), l'ho rielaborata per proporre una soluzione effettiva alla domanda attuale.

Ecco qui:

class walkableDict(dict):
  def walk(self, callback):
    try:
      for key in self:
        self[key] = callback(self[key])
    except TypeError:
      return False
    return True

Uso:

>>> d = walkableDict({ k1: v1, k2: v2 ... })
>>> d.walk(f)

L'idea è di sottoclassare il dict originale per dargli la funzionalità desiderata: "mappare" una funzione su tutti i valori.

Il punto positivo è che questo dizionario può essere utilizzato per archiviare i dati originali come se fosse a dict, trasformando al contempo tutti i dati su richiesta con un callback.

Naturalmente, sentiti libero di nominare la classe e la funzione nel modo desiderato (il nome scelto in questa risposta è ispirato alla array_walk()funzione di PHP ).

Nota: Né il blocco try- exceptné le returnistruzioni sono obbligatorie per la funzionalità, sono lì per imitare ulteriormente il comportamento dei PHP array_walk.


1
Questo non riesce a risolvere la domanda OP poiché il __missing__metodo non verrà chiamato per le chiavi esistenti, che vogliamo trasformare, a meno che il metodo factory passato utilizzi in qualche modo il dict di origine come fallback, ma poiché ciò non fa parte dell'uso dell'esempio, Ritengo che questa sia una risposta insoddisfacente al problema attuale.
Kaos,

Quali chiavi esistenti?
7heo.tk,

Dal OP: Given a dictionary { k1: v1, k2: v2 ... } .... Cioè, hai già un dictinizio ...
Kaos,

Vorrei dire che abbiamo entrambi ragione; ma credo che entrambi abbiamo torto. Hai ragione nel dire che la mia risposta non risponde alla domanda; ma non per il motivo che hai invocato. Ho semplicemente perso il punto, dando un modo per ottenere {v1: f(v1), v2: f(v2), ...}dato [v1, v2, ...], e non dato un dettato. Modificherò la mia risposta per correggerla.
7heo.tk,

2

Per evitare di indicizzare dall'interno di lambda, come:

rval = dict(map(lambda kv : (kv[0], ' '.join(kv[1])), rval.iteritems()))

Puoi anche fare:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))

Questa è una manipolazione intelligente all'interno della stessa 2-tupla nel secondo esempio. Tuttavia, utilizza il disimballaggio automatico della tupla all'interno della lambda, che non è più supportato in Python 3. Pertanto lambda(k,v)non funzionerà. Vedi stackoverflow.com/questions/21892989/…
Jonathan Komar il

0

Sono appena arrivato in questo caso d'uso. Ho implementato la risposta di Gens , aggiungendo un approccio ricorsivo per la gestione di valori che sono anche dicts:

def mutate_dict_in_place(f, d):
    for k, v in d.iteritems():
        if isinstance(v, dict):
            mutate_dict_in_place(f, v)
        else:
            d[k] = f(v)

# Exemple handy usage
def utf8_everywhere(d):
    mutate_dict_in_place((
        lambda value:
            value.decode('utf-8')
            if isinstance(value, bytes)
            else value
        ),
        d
    )

my_dict = {'a': b'byte1', 'b': {'c': b'byte2', 'd': b'byte3'}}
utf8_everywhere(my_dict)
print(my_dict)

Questo può essere utile quando si ha a che fare con file json o yaml che codificano stringhe come byte in Python 2

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.