Domanda Come unire due dizionari in una singola espressione?


Ho due dizionari Python e voglio scrivere una singola espressione che restituisca questi due dizionari, uniti. Il update() il metodo sarebbe quello di cui ho bisogno, se restituisse il risultato invece di modificare un dettt 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 il dt finale unito zno x?

(Per essere extra-chiaro, l'ultimo-vince la gestione del conflitto di dict.update() è quello che sto cercando.)


3210
2017-09-02 07:44


origine


risposte:


Come posso unire due dizionari Python in una singola espressione?

Per i dizionari x e y, z diventa un dizionario unito con valori da y sostituendo quelli da x.

  • In Python 3.5 o versioni successive,:

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • In Python 2, (o 3.4 o inferiore) 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

    z = merge_two_dicts(x, y)
    

Spiegazione

Supponi di avere due dicts e vuoi unirli in un nuovo dict senza alterare i dict 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 che sovrascrivono quelli del primo.

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

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

z = {**x, **y}

Ed è davvero una singola espressione. Ora sta mostrando come implementato nel programma di rilascio per 3.5, PEP 478e ora si è fatto strada Novità di Python 3.5 documento.

Tuttavia, dal momento che molte organizzazioni sono ancora in Python 2, potresti desiderare di farlo in un modo compatibile con le versioni precedenti. Il modo classico Pythonic, disponibile in Python 2 e Python 3.0-3.4, è quello di fare questo come un processo in due fasi:

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

In entrambi gli approcci, y arriverà secondo e i suoi valori andranno a sostituire xI valori di, quindi 'b' punterà a 3 nel nostro risultato finale.

Non ancora su Python 3.5, ma vuoi un singola espressione

Se non sei ancora su Python 3.5, o hai bisogno di scrivere codice compatibile con le versioni precedenti, e lo vuoi in un file singola espressione, il più performante mentre l'approccio 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 quindi hai una singola espressione:

z = merge_two_dicts(x, y)

Puoi anche creare una funzione per unire un numero indefinito di dict, 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 dict. per esempio. dati dicts a a g:

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

e coppie di valori chiave in g avrà la precedenza su dict a a f, e così via.

Critiche di altre risposte

Non utilizzare ciò che vedi nella risposta precedentemente accettata:

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

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

>>> 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 esplicitamente crearli come elenchi, ad es. z = dict(list(x.items()) + list(y.items())). Questo è uno spreco di risorse e potere di calcolo.

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

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

Questo esempio dimostra cosa succede quando i valori sono inattivi:

>>> 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 di 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 usa il dict costruttore, ed è molto veloce ed efficiente in termini di memoria (anche leggermente più del nostro processo in due fasi) ma a meno che tu non sappia esattamente cosa sta succedendo qui (cioè, il secondo dict viene passato come argomento parola chiave al costruttore dict), è difficile da leggere, non è l'uso previsto e quindi non è Pythonic.

Ecco un esempio dell'utilizzo dell'essere risanato in django.

I dadi sono destinati a prendere chiavi lavabili (ad esempio frozensets 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

Dal mailing listGuido van Rossum, il creatore della lingua, ha scritto:

Sto bene con   dichiarare il ditt ({}, ** {1: 3}) illegale, dato che dopotutto è un abuso di   il ** meccanismo.

e

Apparentemente dict (x, ** y) va in giro come "cool hack" per "call"   x.aggiornamento (y) e ritorno x ". Personalmente lo trovo più spregevole di   freddo.

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

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

invece di

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

Risposta ai commenti

Nonostante quello che dice Guido, dict(x, **y) è in linea con la specifica dict, che btw. funziona sia per Python 2 che per 3. Il fatto che funzioni solo per le chiavi stringa è una diretta conseguenza del modo in cui i parametri delle parole chiave funzionano e non di uno short-comming di dict. Né sta usando l'operatore ** in questo luogo un abuso del meccanismo, in realtà ** è stato progettato proprio per passare dicts come parole chiave.

Di nuovo, non funziona per 3 quando le chiavi sono non-stringhe. Il contratto di chiamata implicita è che gli spazi dei nomi assumono le ordinarie dict, mentre gli utenti devono solo passare gli argomenti delle parole chiave che sono stringhe. Tutti gli altri callables lo hanno imposto. dict ha 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 è stata negativa date le altre implementazioni di Python (Pypy, Jython, IronPython). Quindi è stato corretto in Python 3, poiché questo utilizzo potrebbe essere un cambiamento sostanziale.

Vi presento che è un'incompetenza maliziosa scrivere intenzionalmente un codice che funziona solo in una versione di una lingua o che funziona solo con determinati vincoli arbitrari.

Un altro commento:

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

La mia risposta: merge_two_dicts(x, y) sembra davvero molto più chiaro per me, se in realtà ci preoccupiamo della leggibilità. E non è compatibile con le versioni precedenti, poiché Python 2 è sempre più deprecato.

Ad-hoc meno performanti ma corretti

Questi approcci sono meno performanti, ma forniranno un comportamento corretto. Essi saranno molto meno performante di copy e update o il nuovo spacchettamento perché iterano su ciascuna coppia chiave-valore a un livello più alto di astrazione, ma loro fare rispettare l'ordine di precedenza (questi ultimi hanno la precedenza)

Puoi anche collegare manualmente i dadi all'interno di una comprensione del ditt:

{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à 2.4 quando sono state introdotte le espressioni del generatore):

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

itertools.chain raggrupperà 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 è fatto 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


3333
2017-11-10 22:11



Nel tuo caso, quello che puoi fare è:

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

Questo, come tu vuoi, metti l'ultima parola in ze crea il valore per la chiave b essere sovrascritto correttamente dal secondo (y) valore di 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. Creare z:

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

1438
2017-09-02 07:50



Un'alternativa:

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

546
2017-09-02 13:00



Un'altra opzione più concisa:

z = dict(x, **y)

Nota: questa è diventata una risposta popolare, ma è importante sottolineare che se y ha delle chiavi non stringa, il fatto che questo funzioni è un abuso di un dettaglio di implementazione di CPython, e non funziona in Python 3, o in PyPy, IronPython o Jython. Anche, Guido non è un fan. Quindi non posso raccomandare questa tecnica per il codice portatile forward-compatibile o cross-implementation, il che significa che dovrebbe essere evitato del tutto.


272
2017-09-02 15:52



Probabilmente questa non sarà una risposta popolare, ma quasi certamente non vuoi farlo. Se si desidera una copia che è un'unione, quindi utilizzare la copia (o deepcopy, a seconda di cosa vuoi) e quindi aggiornare. Le due righe di codice sono molto più leggibili - più Pythonic - rispetto alla creazione di una singola riga con .items () + .items (). L'esplicito è meglio che implicito.

Inoltre, quando usi .items () (pre Python 3.0), stai creando un nuovo elenco che contiene gli elementi del dict. Se i tuoi dizionari sono grandi, allora è un bel po 'di overhead (due grandi liste che verranno buttate via non appena viene creata la dict unita). update () può funzionare in modo più efficiente, perché può essere eseguito attraverso la seconda voce articolo per articolo.

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 ne vale la pena per 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 () funzionano nelle versioni precedenti.


167
2017-09-08 11:16



In una risposta di follow-up, hai chiesto informazioni sul rendimento relativo di queste due alternative:

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

Sulla mia macchina, almeno (un abbastanza comune x86_64 con Python 2.5.2), alternativa z2 non è solo più breve e più semplice, ma anche significativamente più veloce. Puoi verificarlo da solo usando il timeit modulo che viene fornito con Python.

Esempio 1: dizionari identici che associano 20 interi consecutivi a 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

z2 vince per un fattore di 3,5 circa. Dizionari diversi sembrano dare risultati piuttosto diversi, ma z2 sembra sempre venire avanti. (Se ottieni risultati incoerenti per stesso prova, prova a passare -r con un numero maggiore del valore predefinito 3.)

Esempio 2: dizionari non sovrapposti che associano 252 stringhe brevi a 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 di 10. Questa è una vittoria piuttosto grande nel mio libro!

Dopo aver confrontato quei due, mi chiedevo se z1Le scarse prestazioni potrebbero essere attribuite al sovraccarico di costruzione delle due liste di articoli, il che a sua volta mi ha portato a chiedermi se questa variazione potrebbe 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

conducimi a concludere questo z3 è un po 'più veloce di z1, ma non così veloce come z2. Sicuramente non vale la pena di digitare

In questa discussione manca ancora qualcosa di importante, che è un confronto tra prestazioni di queste alternative con il modo "ovvio" di unire due liste: usando il update metodo. Per cercare di mantenere le cose su un piano di parità con le espressioni, nessuna delle quali modifica x o y, ho intenzione di fare 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, z0 e z2 sembra avere prestazioni essenzialmente identiche. Pensi che questa potrebbe essere una coincidenza? Io non....

In effetti, direi che è impossibile per il puro codice Python fare meglio di così. E se si può fare molto meglio in un modulo di estensione C, immagino che la gente di Python potrebbe essere interessata a incorporare il proprio codice (o una variante del proprio approccio) nel core di Python. Python usa dict in molti posti; ottimizzare le sue operazioni è un grosso problema.

Potresti anche scrivere questo come

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

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


116
2017-10-23 02:38



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

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

86
2017-09-04 19:08



In Python 3, puoi usare collections.ChainMap che raggruppa più dicts o altri mapping per creare un'unica vista aggiornabile:

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

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

73
2018-04-28 03:15



Aggiornamento ricorsivo / approfondito di un dett

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.


61
2017-11-29 11:52