Come unire due dizionari in una singola espressione in Python?


4788

Ho due dizionari Python e voglio scrivere una singola espressione che restituisca questi due dizionari, uniti. Il update()metodo sarebbe quello di cui ho bisogno, se restituisse il suo risultato invece di modificare un dizionario sul posto.

>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

Come posso ottenere quel dizionario unito finale z, no x?

(Per essere più chiari, l'ultimo che vince la gestione dei conflitti dict.update()è anche quello che sto cercando.)


1
Nel caso in cui tu stia usando l'alfa Python 3.9, usa semplicementez = x | y
The Daleks il

Risposte:


5708

Come posso unire due dizionari Python in una singola espressione?

Per i dizionari xe y, zdiventa un dizionario poco unito con valori da ysostituire quelli da x.

  • In Python 3.5 o versioni successive:

    z = {**x, **y}
  • In Python 2, (o 3.4 o precedente) scrivi una funzione:

    def merge_two_dicts(x, y):
        z = x.copy()   # start with x's keys and values
        z.update(y)    # modifies z with y's keys and values & returns None
        return z

    e adesso:

    z = merge_two_dicts(x, y)
  • In Python 3.9.0a4 o versioni successive (data di rilascio finale circa ottobre 2020): PEP-584 , discusso qui , è stato implementato per semplificare ulteriormente questo:

    z = x | y          # NOTE: 3.9+ ONLY

Spiegazione

Supponi di avere due dicts e desideri unirli in un nuovo dict senza alterare i dicts originali:

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

Il risultato desiderato è ottenere un nuovo dizionario ( z) con i valori uniti e i valori del secondo dict sovrascrivendo quelli del primo.

>>> z
{'a': 1, 'b': 3, 'c': 4}

Una nuova sintassi per questo, proposta in PEP 448 e disponibile a partire da Python 3.5 , è

z = {**x, **y}

Ed è davvero una singola espressione.

Nota che possiamo unirci anche con la notazione letterale:

z = {**x, 'foo': 1, 'bar': 2, **y}

e adesso:

>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}

Ora viene mostrato come implementato nel programma di rilascio per 3.5, PEP 478 , e ora si è fatto strada nel documento Novità di Python 3.5 .

Tuttavia, poiché molte organizzazioni sono ancora su Python 2, potresti volerlo fare in modo compatibile con le versioni precedenti. Il modo classico di Pythonic, disponibile in Python 2 e Python 3.0-3.4, consiste nel fare questo in due passaggi:

z = x.copy()
z.update(y) # which returns None since it mutates z

In entrambi gli approcci, yarriverà secondo e i suoi valori sostituiranno quelli x, quindi 'b'indicheranno il 3nostro risultato finale.

Non ancora su Python 3.5, ma vuoi una singola espressione

Se non sei ancora su Python 3.5 o hai bisogno di scrivere codice compatibile con le versioni precedenti e lo desideri in una singola espressione , l'approccio più performante e corretto è metterlo in una funzione:

def merge_two_dicts(x, y):
    """Given two dicts, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

e poi hai una sola espressione:

z = merge_two_dicts(x, y)

Puoi anche creare una funzione per unire un numero indefinito di dadi, da zero a un numero molto grande:

def merge_dicts(*dict_args):
    """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

Questa funzione funzionerà in Python 2 e 3 per tutti i dicts. ad esempio dati forniti aa g:

z = merge_dicts(a, b, c, d, e, f, g) 

e chiave a coppie di valori gavranno precedenza su dicts aa f, e così via.

Critiche di altre risposte

Non usare ciò che vedi nella risposta precedentemente accettata:

z = dict(x.items() + y.items())

In Python 2, crei due elenchi in memoria per ogni dict, crei un terzo elenco in memoria con una lunghezza uguale alla lunghezza dei primi due messi insieme, quindi scarti tutti e tre gli elenchi per creare il dict. In Python 3, questo fallirà perché stai aggiungendo due dict_itemsoggetti insieme, non due elenchi -

>>> c = dict(a.items() + b.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'

e dovresti crearli esplicitamente come elenchi, ad es z = dict(list(x.items()) + list(y.items())). Questo è uno spreco di risorse e potere di calcolo.

Allo stesso modo, prendere l'unione di items()in Python 3 ( viewitems()in Python 2.7) fallirà anche quando i valori sono oggetti non lavabili (come gli elenchi, per esempio). Anche se i tuoi valori sono hash, poiché gli insiemi sono semanticamente non ordinati, il comportamento è indefinito rispetto alla precedenza. Quindi non farlo:

>>> c = dict(a.items() | b.items())

Questo esempio dimostra cosa succede quando i valori non sono lavabili:

>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Ecco un esempio in cui y dovrebbe avere la precedenza, ma invece il valore di x viene mantenuto a causa dell'ordine arbitrario degli insiemi:

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

Un altro trucco che non dovresti usare:

z = dict(x, **y)

Questo utilizza il dictcostruttore ed è molto veloce ed efficiente in termini di memoria (anche leggermente più del nostro processo in due passaggi) ma a meno che tu non sappia esattamente cosa sta succedendo qui (ovvero, il secondo dict viene passato come argomento di parole chiave al dict costruttore), è difficile da leggere, non è l'uso previsto e quindi non è Pythonic.

Ecco un esempio dell'uso corretto in django .

I Dicts sono destinati a prendere chiavi hash (es. Frozenset o tuple), ma questo metodo fallisce in Python 3 quando le chiavi non sono stringhe.

>>> c = dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

Dalla mailing list , Guido van Rossum, il creatore della lingua, scrisse:

Sto bene dichiarando illegale dict ({}, ** {1: 3}), dal momento che dopo tutto si tratta di un abuso del meccanismo **.

e

Apparentemente dict (x, ** y) sta andando in giro come "cool hack" per "chiama x.update (y) e restituisce x". Personalmente lo trovo più spregevole che bello.

È mia comprensione (così come la comprensione del creatore della lingua ) che l'uso previsto dict(**y)è per la creazione di dicts a fini di leggibilità, ad esempio:

dict(a=1, b=10, c=11)

invece di

{'a': 1, 'b': 10, 'c': 11}

Risposta ai commenti

Nonostante ciò che dice Guido, dict(x, **y)è in linea con le specifiche del dict, che tra l'altro. funziona sia per Python 2 che per 3. Il fatto che questo funzioni solo per le chiavi di stringa è una conseguenza diretta del funzionamento dei parametri delle parole chiave e non una breve introduzione di dict. Né sta usando l'operatore ** in questo luogo un abuso del meccanismo, infatti ** è stato progettato proprio per passare a parole chiave come parole chiave.

Ancora una volta, non funziona per 3 quando le chiavi sono non stringhe. Il contratto di chiamata implicito è che gli spazi dei nomi prendono dicts ordinari, mentre gli utenti devono solo passare argomenti di parole chiave che sono stringhe. Tutti gli altri callable lo hanno imposto. dictha rotto questa coerenza in Python 2:

>>> foo(**{('a', 'b'): None})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
>>> dict(**{('a', 'b'): None})
{('a', 'b'): None}

Questa incoerenza era negativa date altre implementazioni di Python (Pypy, Jython, IronPython). Quindi è stato risolto in Python 3, poiché questo utilizzo potrebbe essere un cambiamento radicale.

Sottolineo che è incompetenza malevola scrivere intenzionalmente codice che funziona solo in una versione di una lingua o che funziona solo a causa di determinati vincoli arbitrari.

Più commenti:

dict(x.items() + y.items()) è ancora la soluzione più leggibile per Python 2. La leggibilità conta.

La mia risposta: in merge_two_dicts(x, y)realtà mi sembra molto più chiaro, se in realtà siamo preoccupati per la leggibilità. E non è compatibile con il futuro, poiché Python 2 è sempre più deprecato.

{**x, **y}non sembra gestire dizionari nidificati. il contenuto delle chiavi nidificate viene semplicemente sovrascritto, non unito [...] Ho finito per essere bruciato da queste risposte che non si fondono in modo ricorsivo e sono rimasto sorpreso dal fatto che nessuno lo abbia menzionato. Nella mia interpretazione della parola "fusione" queste risposte descrivono "l'aggiornamento di un detto con un altro" e non la fusione.

Sì. Devo rimandarti alla domanda, che richiede una fusione superficiale di due dizionari, con i valori del primo che vengono sovrascritti dal secondo - in una singola espressione.

Supponendo due dizionari di dizionari, si potrebbe fonderli in modo ricorsivo in una singola funzione, ma si dovrebbe fare attenzione a non modificare i dicts da una delle due fonti, e il modo più sicuro per evitarlo è fare una copia quando si assegnano valori. Poiché le chiavi devono essere hash e di solito non sono modificabili, è inutile copiarle:

from copy import deepcopy

def dict_of_dicts_merge(x, y):
    z = {}
    overlapping_keys = x.keys() & y.keys()
    for key in overlapping_keys:
        z[key] = dict_of_dicts_merge(x[key], y[key])
    for key in x.keys() - overlapping_keys:
        z[key] = deepcopy(x[key])
    for key in y.keys() - overlapping_keys:
        z[key] = deepcopy(y[key])
    return z

Uso:

>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}

Presentare contingenze per altri tipi di valore va ben oltre lo scopo di questa domanda, quindi ti indicherò la mia risposta alla domanda canonica su una "Unione di dizionari di dizionari" .

Ad-hocs meno performanti ma corretti

Questi approcci sono meno performanti, ma forniranno un comportamento corretto. Saranno molto meno performante rispetto copye updateo la nuova disimballaggio perché iterazioni su ogni coppia di valori-chiave a un livello più elevato di astrazione, ma fare rispettare l'ordine di precedenza (quest'ultimo dicts prevalgono)

Puoi anche incatenare manualmente i dadi all'interno di una comprensione del dict:

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

o in python 2.6 (e forse già nel 2.4 quando furono introdotte le espressioni del generatore):

dict((k, v) for d in dicts for k, v in d.items())

itertools.chain incrocerà gli iteratori sulle coppie chiave-valore nell'ordine corretto:

import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))

Analisi di performance

Farò solo l'analisi delle prestazioni degli usi noti per comportarsi correttamente.

import timeit

Quanto segue viene eseguito su Ubuntu 14.04

In Python 2.7 (sistema Python):

>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934

In Python 3.5 (deadsnakes PPA):

>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287

Risorse sui dizionari


9
@MohammadAzim "solo stringhe" si applica solo all'espansione dell'argomento della parola chiave in callable, non alla sintassi di decompressione generalizzata. Per dimostrare che funziona: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall

37
risposte brevi z = {**x, **y}mi stimolano davvero
pcko1

1
Questo può essere modificato quando si accetta PEP-0584. Verrà implementato un nuovo operatore sindacale con la seguente sintassi:x | y
Callam Delaney,

2
Quando una risposta necessita di un riepilogo in alto, è troppo lunga.
Gringo Suave, l'

2
Ciao, la parte superiore è un riepilogo, sì. Sta a te. Il tutto sarebbe un ottimo post sul blog. Nota Py 3.4 e precedenti sono EOL, 3.5 si avvicina a EOL nel 2020-09.
Gringo Suave,

1618

Nel tuo caso, quello che puoi fare è:

z = dict(x.items() + y.items())

Questo, come lo desideri, inserirà il dict finale ze farà sì che il valore per key bvenga sovrascritto correttamente dal valore del secondo ( y) dict:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

Se usi Python 3, è solo un po 'più complicato. Per creare z:

>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

Se si utilizza Python versione 3.9.0a4 o successiva, è possibile utilizzare direttamente:

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = x | y
print(z)

Output: {'a': 1, 'c': 11, 'b': 10}

3
Non usarlo perché è molto inefficiente. (Vedi i risultati timeit di seguito.) Potrebbe essere stato necessario nei giorni Py2 se una funzione wrapper non fosse un'opzione, ma quei giorni sono ormai passati.
Gringo Suave l'

632

Un'alternativa:

z = x.copy()
z.update(y)

83
Per chiarire perché questo non soddisfa i criteri forniti dalla domanda: non è una singola espressione e non restituisce z.
Alex

2
@neuronet ogni oneliner di solito sta semplicemente spostando il codice che deve accadere in un componente diverso e lo risolve lì. questo è sicuramente uno dei casi. ma altre lingue hanno costrutti più belli di Python per questo. e avere una variante referenzialmente trasparente che restituisce il suo elemento è bello avere qualcosa.
Alex

12
Mettilo in questo modo: se hai bisogno di mettere due righe di commenti che spiegano la tua riga di codice alle persone a cui dai il codice ... l'hai fatto davvero in una riga? :) Concordo pienamente sul fatto che Python non sia adatto a questo: dovrebbe esserci un modo molto più semplice. Mentre questa risposta è più pitonica, è davvero tutto così esplicito o chiaro? Updatenon è una delle funzioni "core" che le persone tendono ad usare molto.
eric,

Bene, se le persone insistono per renderlo un vero e proprio linea, puoi sempre fare (lambda z: z.update(y) or z)(x.copy()): P
tra il

341

Un'altra opzione più concisa:

z = dict(x, **y)

Nota : questa è diventata una risposta popolare, ma è importante sottolineare che se yha delle chiavi non stringa, il fatto che funzioni affatto è un abuso di un dettaglio dell'implementazione di CPython e non funziona in Python 3, o in PyPy, IronPython o Jython. Inoltre, Guido non è un fan . Quindi non posso raccomandare questa tecnica per il codice portatile compatibile con le versioni successive o per l'implementazione, il che significa che dovrebbe essere assolutamente evitato.


Funziona bene in Python 3 e PyPy e PyPy 3 , non posso parlare con Jython o Iron. Dato che questo modello è esplicitamente documentato (vedere il terzo modulo di costruzione in questa documentazione), direi che non è un "dettaglio di implementazione" ma un uso intenzionale delle funzionalità.
amcgregor,

5
@amcgregor Hai perso la frase "se y ha chiavi non stringa". Questo è ciò che non funziona in Python3; il fatto che funzioni in CPython 2 è un dettaglio di implementazione su cui non si può fare affidamento. IFF garantisce che tutte le tue chiavi siano stringhe, questa è un'opzione completamente supportata.
Carl Meyer,

214

Questa probabilmente non sarà una risposta popolare, ma quasi sicuramente non vuoi farlo. Se vuoi una copia che sia un'unione, usa copia (o deepcopy , a seconda di ciò che desideri) e quindi aggiorna. Le due righe di codice sono molto più leggibili - più Pythonic - rispetto alla creazione di una sola riga con .items () + .items (). Esplicito è meglio che implicito.

Inoltre, quando si utilizza .items () (pre Python 3.0), si crea un nuovo elenco che contiene gli elementi del dict. Se i tuoi dizionari sono grandi, allora questo è un sacco di sovraccarico (due grandi liste che verranno gettate via non appena viene creato il dict unito). update () può funzionare in modo più efficiente, poiché può eseguire il secondo dict elemento per elemento.

In termini di tempo :

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

IMO il piccolo rallentamento tra i primi due vale la leggibilità. Inoltre, gli argomenti delle parole chiave per la creazione del dizionario sono stati aggiunti solo in Python 2.3, mentre copy () e update () funzioneranno nelle versioni precedenti.


150

In una risposta di follow-up, hai chiesto delle prestazioni relative di queste due alternative:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

Sulla mia macchina, almeno (un x86_64 abbastanza ordinario con Python 2.5.2), l'alternativa z2non è solo più breve e semplice, ma anche significativamente più veloce. Puoi verificarlo tu stesso usando il timeitmodulo fornito con Python.

Esempio 1: dizionari identici che mappano 20 numeri interi consecutivi su se stessi:

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)' 
100000 loops, best of 3: 1.53 usec per loop

z2vince per un fattore di 3,5 o giù di lì. Diversi dizionari sembrano dare risultati abbastanza diversi, ma z2sembrano sempre venire avanti. (Se ottieni risultati incoerenti per lo stesso test, prova a passare -rcon un numero maggiore del 3. predefinito)

Esempio 2: dizionari non sovrapposti che mappano 252 stringhe brevi su numeri interi e viceversa:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'               
10000 loops, best of 3: 26.9 usec per loop

z2 vince di circa un fattore 10. È una vittoria abbastanza grande nel mio libro!

Dopo aver confrontato questi due, mi chiedevo se z1le scarse prestazioni potessero essere attribuite al sovraccarico di costruzione dei due elenchi di articoli, il che a sua volta mi ha portato a chiedermi se questa variazione potesse funzionare meglio:

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

Alcuni test rapidi, ad es

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

portami a concludere che z3è un po 'più veloce di z1, ma non così veloce come z2. Sicuramente non vale tutta la digitazione extra.

In questa discussione manca ancora qualcosa di importante, che è un confronto delle prestazioni di queste alternative con il modo "ovvio" di unire due elenchi: usando il updatemetodo. Per provare a mantenere le cose su un piano di parità con le espressioni, nessuna delle quali modifica x o y, farò una copia di x invece di modificarla sul posto, come segue:

z0 = dict(x)
z0.update(y)

Un risultato tipico:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

In altre parole, z0e z2sembrano avere prestazioni sostanzialmente identiche. Pensi che questa potrebbe essere una coincidenza? Io non....

In effetti, arriverei al punto di affermare che è impossibile per il codice Python puro fare di meglio. E se riesci a fare significativamente meglio in un modulo di estensione C, immagino che la gente di Python potrebbe essere interessata a incorporare il tuo codice (o una variante del tuo approccio) nel core di Python. Python utilizza dictin molti luoghi; ottimizzare le sue operazioni è un grosso problema.

Puoi anche scrivere questo come

z0 = x.copy()
z0.update(y)

come fa Tony, ma (non a caso) la differenza nella notazione risulta non avere alcun effetto misurabile sulle prestazioni. Usa quello che ti sembra giusto. Certo, ha assolutamente ragione a sottolineare che la versione a due istruzioni è molto più facile da capire.


5
Questo non funziona in Python 3; items()non è catenabile e iteritemsnon esiste.
Antti Haapala,

127

In Python 3.0 ecollections.ChainMap versioni successive , puoi utilizzare i gruppi che raggruppano più dicts o altri mapping insieme per creare un'unica vista aggiornabile:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(ChainMap({}, y, x))
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11

Aggiornamento per Python 3.5 e versioni successive : è possibile utilizzare l' imballaggio e il disimballaggio del dizionario esteso PEP 448 . Questo è semplice e veloce:

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}

3
Ma si dovrebbe essere cauti mentre si utilizza ChainMap c'è un problema: se si hanno chiavi duplicate, vengono utilizzati i valori della prima mappatura e quando si chiama dela si dice che una ChainMap c cancellerà la prima mappatura di quella chiave.
Uccisore

7
@Prerito Cos'altro ti aspetteresti che faccia? Questo è il modo normale in cui funzionano gli spazi dei nomi concatenati. Considera come funziona $ PATH in bash. L'eliminazione di un eseguibile sul percorso non preclude un altro eseguibile con lo stesso nome più a monte.
Raymond Hettinger,

2
@Raymond Hettinger Sono d'accordo, ho appena aggiunto un avvertimento. Molte persone potrebbero non saperlo. : D
Cacciatrice

@Prerito Puoi dictevitarlo, per esempio:dict(ChainMap({}, y, x))
wjandrea,

113

Volevo qualcosa di simile, ma con la possibilità di specificare come unire i valori delle chiavi duplicate, quindi l'ho hackerato (ma non l'ho testato pesantemente). Ovviamente questa non è una singola espressione, ma è una singola chiamata di funzione.

def merge(d1, d2, merge_fn=lambda x,y:y):
    """
    Merges two dictionaries, non-destructively, combining 
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous 
    types in your dicts, so specifying a merge technique can be 
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

    """
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result

88

Aggiornamento ricorsivo / profondo di un dict

def deepupdate(original, update):
    """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
    """
    for key, value in original.iteritems(): 
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key]) 
    return update

Dimostrazione:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

Uscite:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

Grazie rednaw per le modifiche.


1
Questo non risponde alla domanda. La domanda richiede chiaramente un nuovo dizionario, z, dai dizionari originali, xey, con i valori di y che sostituiscono quelli di x - non un dizionario aggiornato. Questa risposta modifica y sul posto aggiungendo valori da x. Peggio ancora, non copia questi valori, quindi è possibile modificare ulteriormente il dizionario modificato, ye le modifiche potrebbero riflettersi nel dizionario x. @ Jérôme Spero che questo codice non causi alcun bug per la tua applicazione - almeno considera di usare deepcopy per copiare i valori.
Aaron Hall

1
@AaronHall ha concordato che questo non risponde alla domanda. Ma risponde al mio bisogno. Capisco queste limitazioni, ma non è un problema nel mio caso. Pensandoci, forse il nome è fuorviante, in quanto potrebbe evocare una deepcopy, che non fornisce. Ma affronta la nidificazione profonda. Ecco un'altra implementazione da Martellibot: stackoverflow.com/questions/3232943/… .
Jérôme,

72

La migliore versione che potrei pensare mentre non uso copia sarebbe:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

È più veloce di, dict(x.items() + y.items())ma non così veloce n = copy(a); n.update(b), almeno su CPython. Questa versione funziona anche in Python 3 se cambi iteritems()a items(), che viene automaticamente eseguita dallo strumento 2to3.

Personalmente mi piace di più questa versione perché descrive abbastanza bene ciò che voglio in una singola sintassi funzionale. L'unico problema minore è che non rende del tutto evidente che i valori di y hanno la precedenza sui valori di x, ma non credo sia difficile capirlo.


71

Python 3.5 (PEP 448) consente un'opzione di sintassi migliore:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

O anche

final = {'a': 1, 'b': 1, **x, **y}

In Python 3.9 usi anche | e | = con l'esempio seguente di PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}

In che modo questa soluzione è migliore della dict(x, **y)soluzione? Come tu (@CarlMeyer) hai menzionato nella nota della tua risposta ( stackoverflow.com/a/39858/2798610 ) Guido considera tale soluzione illegale .
Blackeagle52

14
A Guido non piace dict(x, **y)per il (molto buono) motivo per cui si basa ysolo su chiavi che sono nomi di argomenti di parole chiave validi (a meno che non si stia usando CPython 2.7, dove il costruttore di dict imbroglia). Questa obiezione / restrizione non si applica a PEP 448, che generalizza la **sintassi di decompressione ai letterali dict. Quindi questa soluzione ha la stessa concisione di dict(x, **y), senza il rovescio della medaglia.
Carl Meyer,

62
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

Per gli elementi con i tasti in entrambi i dizionari ('b'), puoi controllare quale finisce nell'output mettendolo per ultimo.


In python 3 otterrai TypeError: tipi di operando non supportati per +: 'dict_items' e 'dict_items' ... dovresti incapsulare ogni dict con list () come: dict (list (x.items ()) + list (y.items ()))
justSaid

49

Sebbene alla domanda sia già stata data risposta più volte, questa semplice soluzione al problema non è stata ancora elencata.

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

È veloce come z0 e la malvagia z2 di cui sopra, ma facile da capire e cambiare.


3
ma sono tre affermazioni piuttosto che un'espressione
fortran,

14
Sì! Le citate soluzioni a espressione singola sono lente o cattive. Il buon codice è leggibile e mantenibile. Quindi il problema è la domanda non la risposta. Dovremmo chiedere la migliore soluzione di un problema, non una soluzione a una riga.
phobie,

7
Perdere z4 = {}e cambiare la riga successiva in z4 = x.copy()- meglio di un buon codice non fa cose inutili (il che lo rende ancora più leggibile e gestibile).
martineau,

3
Il tuo suggerimento cambierebbe questo in risposta a Matthews. Mentre la sua risposta va bene, penso che la mia sia più leggibile e meglio mantenibile. La linea aggiuntiva sarebbe male solo se costerebbe tempo di esecuzione.
phobie,

47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Tra queste risposte oscure e dubbie, questo brillante esempio è l'unico e unico modo per unire i dadi in Python, approvato dal dittatore a vita Guido van Rossum stesso! Qualcun altro ha suggerito la metà di questo, ma non lo ha messo in una funzione.

print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

dà:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}

39

Se pensi che i lambda siano cattivi, non leggere oltre. Come richiesto, è possibile scrivere la soluzione rapida ed efficiente in termini di memoria con una sola espressione:

x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

Come suggerito sopra, usare due righe o scrivere una funzione è probabilmente un modo migliore di procedere.


33

Sii pitonico. Usa una comprensione :

z={i:d[i] for d in [x,y] for i in d}

>>> print z
{'a': 1, 'c': 11, 'b': 10}

1
In funzione:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight il

1
Salva una ricerca z={k: v for d in (x, y) for k, v in d.items()}
ripetendo

30

In python3, il itemsmetodo non restituisce più un elenco , ma piuttosto una vista , che agisce come un set. In questo caso dovrai prendere l'unione set poiché concatenare con +non funzionerà:

dict(x.items() | y.items())

Per un comportamento simile a python3 nella versione 2.7, il viewitemsmetodo dovrebbe funzionare al posto di items:

dict(x.viewitems() | y.viewitems())

Preferisco comunque questa notazione poiché sembra più naturale pensarla come un'operazione sindacale prestabilita piuttosto che concatenazione (come mostra il titolo).

Modificare:

Un altro paio di punti per Python 3. Innanzitutto, nota che dict(x, **y) trucco non funzionerà in Python 3 a meno che le chiavi non ysiano stringhe.

Inoltre, la risposta Chainmap di Raymond Hettinger è piuttosto elegante, dal momento che può prendere un numero arbitrario di dadi come argomenti, ma dai documenti sembra che guardi in sequenza un elenco di tutti i dicts per ogni ricerca:

Le ricerche eseguono una ricerca successiva nei mapping sottostanti fino a quando non viene trovata una chiave.

Questo può rallentarti se hai molte ricerche nella tua applicazione:

In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 µs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 µs per loop

Quindi circa un ordine di grandezza più lento per le ricerche. Sono un fan di Chainmap, ma sembra meno pratico dove potrebbero esserci molte ricerche.


22

Abuso che porta a una soluzione a una sola espressione per la risposta di Matthew :

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

Hai detto che volevi un'espressione, quindi ho abusato lambdadi legare un nome e tuplo per scavalcare il limite di una espressione di lambda. Sentiti libero di rabbrividire.

Potresti anche farlo ovviamente se non ti interessa copiarlo:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}

22

Soluzione semplice che utilizza itertools che preserva l'ordine (questi ultimi hanno la precedenza)

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

Ed è uso:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}


16

Anche se le risposte erano buone per questo dizionario superficiale , nessuno dei metodi qui definiti in realtà fa una fusione profonda del dizionario.

Seguono esempi:

a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

Ci si aspetterebbe un risultato di qualcosa del genere:

{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

Invece, otteniamo questo:

{'two': True, 'one': {'extra': False}}

La voce "one" avrebbe dovuto contenere "depth_2" e "extra" come elementi nel suo dizionario se fosse stata effettivamente una fusione.

Utilizzando anche la catena, non funziona:

from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

Risultati in:

{'two': True, 'one': {'extra': False}}

La profonda unione che rcwesick ha dato crea anche lo stesso risultato.

Sì, funzionerà per unire i dizionari di esempio, ma nessuno di essi è un meccanismo generico per unire. Lo aggiornerò più tardi una volta che scrivo un metodo che fa una vera unione.


11

(Solo per Python2.7 *; esistono soluzioni più semplici per Python3 *.)

Se non sei contrario all'importazione di un modulo di libreria standard, puoi farlo

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

(Il or abit in lambdaè necessario perché dict.updateritorna sempre Nonein caso di successo.)


11

Se non ti dispiace mutare x,

x.update(y) or x

Semplice, leggibile, performante. Si sa update() sempre rendimenti None, che è un valore falso. Quindi l'espressione sopra valuterà sempre x, dopo averla aggiornata.

I metodi mutanti nella libreria standard (come .update()) ritornano Noneper convenzione, quindi questo schema funzionerà anche su quelli. Se stai utilizzando un metodo che non segue questa convenzione, orpotrebbe non funzionare. Tuttavia, puoi utilizzare un display e un indice di tupla per renderlo una singola espressione. Funziona indipendentemente da ciò a cui il primo elemento valuta.

(x.update(y), x)[-1]

Se non si dispone ancora xdi una variabile, è possibile utilizzare lambdaper creare un locale senza utilizzare un'istruzione di assegnazione. Ciò equivale a usare lambdacome espressione let , che è una tecnica comune nei linguaggi funzionali, ma forse non ritonica.

(lambda x: x.update(y) or x)({'a': 1, 'b': 2})

Anche se non è così diverso dal seguente uso del nuovo operatore tricheco (solo Python 3.8+):

(x := {'a': 1, 'b': 2}).update(y) or x

Se vuoi una copia, lo stile PEP 448 è il più semplice {**x, **y}. Ma se questo non è disponibile nella tua (vecchia) versione di Python, il pattern let funziona anche qui.

(lambda z: z.update(y) or z)(x.copy())

(Questo è ovviamente equivalente a (z := x.copy()).update(y) or z, ma se la tua versione di Python è abbastanza nuova per questo, allora lo stile PEP 448 sarà disponibile.)


10

Attingendo alle idee qui e altrove ho compreso una funzione:

def merge(*dicts, **kv): 
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

Utilizzo (testato in Python 3):

assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

Invece potresti usare un lambda.


10

Il problema che ho con le soluzioni elencate fino ad oggi è che, nel dizionario unito, il valore per la chiave "b" è 10 ma, a mio modo di pensare, dovrebbe essere 12. Alla luce di ciò, presento quanto segue:

import timeit

n=100000
su = """
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""

def timeMerge(f,su,niter):
    print "{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k] ",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print "confirm b elements are added:",x

risultati:

0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                   
0.150380 sec for: dict(x.items() + y.items())   
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}

1
Potresti essere interessato a cytoolz.merge_with( toolz.readthedocs.io/en/latest/… )
bli

10

È così sciocco che .updatenon restituisce nulla.
Uso una semplice funzione di aiuto per risolvere il problema:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

Esempi:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy

10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

Questo dovrebbe risolvere il tuo problema.


9

Questo può essere fatto con una sola comprensione del dettato:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

A mio avviso, la risposta migliore per la parte "espressione singola" in quanto non sono necessarie funzioni extra ed è breve.


Sospetto che le prestazioni non saranno molto buone; la creazione di un set di ogni dict quindi solo iterando attraverso i tasti significa un'altra ricerca del valore ogni volta (anche se relativamente veloce, aumenta ancora l'ordine della funzione per il ridimensionamento)
Breezer

2
tutto dipende dalla versione del pitone che stiamo usando. In 3.5 e versioni successive {** x, ** y} fornisce il dizionario concatenato
Rashid Mv

9

Ci sarà una nuova opzione quando uscirà Python 3.8 ( previsto per il 20 ottobre 2019 ), grazie a PEP 572: Assignment Expressions . Il nuovo operatore di espressione di assegnazione :=consente di assegnare il risultato di copye lo utilizza ancora per chiamare update, lasciando il codice combinato una singola espressione, anziché due istruzioni, modificando:

newdict = dict1.copy()
newdict.update(dict2)

per:

(newdict := dict1.copy()).update(dict2)

mentre si comporta in modo identico in ogni modo. Se devi anche restituire il risultato dict(hai chiesto un'espressione che restituisce il dict; quanto sopra crea e assegna a newdict, ma non lo restituisce, quindi non puoi usarlo per passare un argomento a una funzione così com'è, a la myfunc((newdict := dict1.copy()).update(dict2))) , quindi aggiungi solo or newdictalla fine (poiché il valore updaterestituito None, che è falso, verrà quindi valutato e restituito newdictcome risultato dell'espressione):

(newdict := dict1.copy()).update(dict2) or newdict

Avvertenza importante: in generale, scoraggerei questo approccio a favore di:

newdict = {**dict1, **dict2}

L'approccio di decompressione è più chiaro (per chiunque sia a conoscenza di decompressione generalizzata in primo luogo, che dovresti ), non richiede affatto un nome per il risultato (quindi è molto più conciso quando si costruisce un temporaneo che viene immediatamente passato a un funzione o incluso in un list/ tupleletterale o simili), ed è quasi certamente più veloce, essendo (su CPython) approssimativamente equivalente a:

newdict = {}
newdict.update(dict1)
newdict.update(dict2)

ma fatto a livello C, usando l' dictAPI concreta , quindi non è coinvolta alcuna ricerca / associazione di metodi dinamici o overhead di invio di chiamate di funzione (dove (newdict := dict1.copy()).update(dict2)è inevitabilmente identico al comportamento del due-liner originale, eseguendo il lavoro in passaggi discreti, con ricerca dinamica / binding / invocazione di metodi.

È anche più estensibile, poiché la fusione di tre dicts è ovvia:

 newdict = {**dict1, **dict2, **dict3}

dove l'uso delle espressioni di assegnazione non si ridimensiona in questo modo; il più vicino che potresti ottenere sarebbe:

 (newdict := dict1.copy()).update(dict2), newdict.update(dict3)

o senza la tupla temporanea di Nones, ma con test di veridicità di ogni Nonerisultato:

 (newdict := dict1.copy()).update(dict2) or newdict.update(dict3)

ciascuno dei quali è ovviamente molto più brutto, e comprende ulteriori inefficienze (o un sprecato temporanea tupledi Nones di separazione virgola o inutile test truthiness di ogni update's Nonecambio di orseparazione).

L'unico vero vantaggio dell'approccio all'espressione di assegnazione si verifica se:

  1. Hai un codice generico che deve gestire sia sets che dicts (entrambi supportano copye update, quindi il codice funziona più o meno come ti aspetteresti)
  2. Ti aspetti di ricevere oggetti simili a dict arbitrari , non solo dictse stesso, e devi preservare il tipo e la semantica del lato sinistro (piuttosto che finire con un semplice dict). Mentre myspecialdict({**speciala, **specialb})potrebbe funzionare, implicherebbe un extra temporaneo dict, e se le myspecialdictcaratteristiche dictnon possono essere conservate (ad es. I normali dictora conservano l'ordine in base alla prima apparizione di una chiave e il valore in base all'ultima apparizione di una chiave; potresti voler uno che conserva l'ordine in base all'ultimoaspetto di una chiave, quindi l'aggiornamento di un valore lo sposta anche alla fine), quindi la semantica sarebbe errata. Poiché la versione dell'espressione di assegnazione utilizza i metodi denominati (che sono presumibilmente sovraccarichi per comportarsi in modo appropriato), non crea mai undictaffatto (a meno che non dict1fosse già a dict), preservando il tipo originale (e la semantica del tipo originale), il tutto evitando qualsiasi temporaneo.

8
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x, z = dict(x), x.update(y) or x
>>> x
{'a': 1, 'b': 2}
>>> y
{'c': 11, 'b': 10}
>>> z
{'a': 1, 'c': 11, 'b': 10}

Questo metodo sovrascrive xcon la sua copia. Se xè un argomento di funzione questo non funzionerà (vedi esempio )
bartolo-otrit
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.