Come unire dizionari di dizionari?


129

Ho bisogno di unire più dizionari, ecco quello che ho per esempio:

dict1 = {1:{"a":{A}}, 2:{"b":{B}}}

dict2 = {2:{"c":{C}}, 3:{"d":{D}}

Con A B CeD essendo foglie dell'albero, come{"info1":"value", "info2":"value2"}

C'è un livello sconosciuto (profondità) di dizionari, potrebbe essere {2:{"c":{"z":{"y":{C}}}}}

Nel mio caso rappresenta una struttura di directory / file con nodi che sono documenti e lascia file.

Voglio unirli per ottenere:

 dict3 = {1:{"a":{A}}, 2:{"b":{B},"c":{C}}, 3:{"d":{D}}}

Non sono sicuro di come avrei potuto farlo facilmente con Python.


Cosa vuoi per la tua profondità arbitraria di dizionari? Vuoi yappiattito fino al clivello o cosa? Il tuo esempio è incompleto.
agf,

Controllare la mia classe NestedDict qui: stackoverflow.com/a/16296144/2334951 Essa gestione delle strutture del dizionario nidificate come la fusione e altro.
SzieberthAdam,

3
Un avvertimento per tutti coloro che cercano soluzioni: questa domanda riguarda solo i dadi nidificati. La maggior parte delle risposte non gestisce correttamente il caso più complicato di elenchi di dadi all'interno della struttura. Se avete bisogno di questo tentativo la risposta di @Osiloke di seguito: stackoverflow.com/a/25270947/1431660
SHernandez


Risposte:


143

questo è in realtà abbastanza complicato - in particolare se si desidera un messaggio di errore utile quando le cose sono incoerenti, accettando correttamente voci duplicate ma coerenti (qualcosa che nessun'altra risposta qui fa ...)

supponendo che tu non abbia un numero enorme di voci, una funzione ricorsiva è la più semplice:

def merge(a, b, path=None):
    "merges b into a"
    if path is None: path = []
    for key in b:
        if key in a:
            if isinstance(a[key], dict) and isinstance(b[key], dict):
                merge(a[key], b[key], path + [str(key)])
            elif a[key] == b[key]:
                pass # same leaf value
            else:
                raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
        else:
            a[key] = b[key]
    return a

# works
print(merge({1:{"a":"A"},2:{"b":"B"}}, {2:{"c":"C"},3:{"d":"D"}}))
# has conflict
merge({1:{"a":"A"},2:{"b":"B"}}, {1:{"a":"A"},2:{"b":"C"}})

si noti che questo muta a- il contenuto di bviene aggiunto a a(che viene anche restituito). se vuoi tenerlo apotresti chiamarlo cosìmerge(dict(a), b) .

agf ha sottolineato (sotto) che potresti avere più di due dadi, nel qual caso puoi usare:

reduce(merge, [dict1, dict2, dict3...])

dove tutto sarà aggiunto a dict1.

[nota - ho modificato la mia risposta iniziale per mutare il primo argomento; che rende il "ridurre" più facile da spiegare]

ps in python 3, ti servirà anche from functools import reduce


1
È quindi possibile inserire questo all'interno di un reduceciclo equivalente o per lavorare con un numero arbitrario di dicts anziché due. Tuttavia, non sono sicuro che questo faccia anche quello che vuole (non era chiaro), ti ritrovi con il 2: {'c': {'z': {'y': {'info1': 'value', 'info2': 'value2'}}}, 'b': {'info1': 'value', 'info2': 'value2'}}suo secondo esempio, non sono sicuro se vuole ze yappiattito o no?
agf,

1
sono strutture di directory quindi non penso che voglia schiacciare qualcosa? oh, scusa, ho perso "dizionari multipli". sì, ridurre sarebbe buono. lo aggiungerò.
Andrew Cooke,

Questo fa esattamente quello che volevo! Mi dispiace di non essere stato abbastanza chiaro ... Pensavo di stare bene con Python, non sembra: - / Avevo bisogno di una funzione ricorsiva a causa dei dadi annidati, questo funziona e posso capirlo :) Non lo so sembra essere in grado di farlo funzionare con riduci però ...
fdhex,

2
Per tutti coloro che con le liste come il livello nidificato finale sotto le dicts, si può fare questo invece di aumentare l'errore di concatenare le due liste: a[key] = a[key] + b[key]. Grazie per la risposta utile
kevinmicke,

1
> se vuoi mantenere un potresti chiamarlo come unisci (dict (a), b) Nota che i dadi nidificati saranno comunque mutati. Per evitare ciò, utilizzare copy.deepcopy.
rcorre,

31

Ecco un modo semplice per farlo utilizzando i generatori:

def mergedicts(dict1, dict2):
    for k in set(dict1.keys()).union(dict2.keys()):
        if k in dict1 and k in dict2:
            if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
                yield (k, dict(mergedicts(dict1[k], dict2[k])))
            else:
                # If one of the values is not a dict, you can't continue merging it.
                # Value from second dict overrides one in first and we move on.
                yield (k, dict2[k])
                # Alternatively, replace this with exception raiser to alert you of value conflicts
        elif k in dict1:
            yield (k, dict1[k])
        else:
            yield (k, dict2[k])

dict1 = {1:{"a":"A"},2:{"b":"B"}}
dict2 = {2:{"c":"C"},3:{"d":"D"}}

print dict(mergedicts(dict1,dict2))

Questo stampa:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}

se vuoi mantenere il tema del generatore potresti incatenare (dict1.keys (), dict2.keys ())
andrew cooke,

Non otterresti chiavi duplicate?
jterrace,

Questo sembra fare il lavoro, almeno sulla mia serie di dati, ma dato che non ho mai capito bene la resa e i generatori, sono praticamente perso sul perché, ma ci proverò un po 'di più, potrebbe essere utile!
fdhex,

ah, sì, otterrebbero chiavi duplicate. avresti ancora bisogno di avvolgerlo in un set, scusa.
Andrew Cooke,

2
L'ho trovato particolarmente utile. Ma il bello sarebbe lasciare che la funzione risolva i conflitti come parametro.
mentatkgs,

25

Un problema con questa domanda è che i valori del dict possono essere pezzi di dati arbitrariamente complessi. Sulla base di queste e altre risposte, ho trovato questo codice:

class YamlReaderError(Exception):
    pass

def data_merge(a, b):
    """merges b into a and return merged result

    NOTE: tuples and arbitrary objects are not handled as it is totally ambiguous what should happen"""
    key = None
    # ## debug output
    # sys.stderr.write("DEBUG: %s to %s\n" %(b,a))
    try:
        if a is None or isinstance(a, str) or isinstance(a, unicode) or isinstance(a, int) or isinstance(a, long) or isinstance(a, float):
            # border case for first run or if a is a primitive
            a = b
        elif isinstance(a, list):
            # lists can be only appended
            if isinstance(b, list):
                # merge lists
                a.extend(b)
            else:
                # append to list
                a.append(b)
        elif isinstance(a, dict):
            # dicts must be merged
            if isinstance(b, dict):
                for key in b:
                    if key in a:
                        a[key] = data_merge(a[key], b[key])
                    else:
                        a[key] = b[key]
            else:
                raise YamlReaderError('Cannot merge non-dict "%s" into dict "%s"' % (b, a))
        else:
            raise YamlReaderError('NOT IMPLEMENTED "%s" into "%s"' % (b, a))
    except TypeError, e:
        raise YamlReaderError('TypeError "%s" in key "%s" when merging "%s" into "%s"' % (e, key, b, a))
    return a

Il mio caso d'uso consiste nell'unire i file YAML in cui ho a che fare solo con un sottoinsieme di possibili tipi di dati. Quindi posso ignorare le tuple e altri oggetti. Per me significa una logica di fusione ragionevole

  • sostituire gli scalari
  • elenchi di append
  • unire i dicts aggiungendo le chiavi mancanti e aggiornando le chiavi esistenti

Tutto il resto e l'imprevisto si traducono in un errore.


1
Fantastico. Funziona bene anche sulle discariche JSON. Ho appena rimosso la gestione degli errori. (Essere pigri, posso fare quelli giusti per Json sono sicuro)
dgBP

3
la sequenza "isinstance" può essere sostituita w / isinstance(a, (str, unicode, int, long, float))isnt 'esso?
simahawk,

12

I dizionari dei dizionari si fondono

Poiché questa è la domanda canonica (nonostante alcune non generalità) sto fornendo l'approccio canonico Pythonic per risolvere questo problema.

Caso più semplice: "le foglie sono dadi nidificati che terminano con dadi vuoti":

d1 = {'a': {1: {'foo': {}}, 2: {}}}
d2 = {'a': {1: {}, 2: {'bar': {}}}}
d3 = {'b': {3: {'baz': {}}}}
d4 = {'a': {1: {'quux': {}}}}

Questo è il caso più semplice per la ricorsione e consiglierei due approcci ingenui:

def rec_merge1(d1, d2):
    '''return new merged dict of dicts'''
    for k, v in d1.items(): # in Python 2, use .iteritems()!
        if k in d2:
            d2[k] = rec_merge1(v, d2[k])
    d3 = d1.copy()
    d3.update(d2)
    return d3

def rec_merge2(d1, d2):
    '''update first dict with second recursively'''
    for k, v in d1.items(): # in Python 2, use .iteritems()!
        if k in d2:
            d2[k] = rec_merge2(v, d2[k])
    d1.update(d2)
    return d1

Credo che preferirei il secondo al primo, ma tieni presente che lo stato originale del primo dovrebbe essere ricostruito dalla sua origine. Ecco l'uso:

>>> from functools import reduce # only required for Python 3.
>>> reduce(rec_merge1, (d1, d2, d3, d4))
{'a': {1: {'quux': {}, 'foo': {}}, 2: {'bar': {}}}, 'b': {3: {'baz': {}}}}
>>> reduce(rec_merge2, (d1, d2, d3, d4))
{'a': {1: {'quux': {}, 'foo': {}}, 2: {'bar': {}}}, 'b': {3: {'baz': {}}}}

Caso complesso: "le foglie sono di qualsiasi altro tipo:"

Quindi, se finiscono in dicts, è un semplice caso di unire i dicts vuoti di fine. Altrimenti, non è così banale. Se le stringhe, come le unisci? I set possono essere aggiornati in modo simile, in modo da poter dare quel trattamento, ma perdiamo l'ordine in cui sono stati uniti. Quindi l'ordine conta?

Quindi al posto di maggiori informazioni, l'approccio più semplice sarà quello di fornire loro il trattamento di aggiornamento standard se entrambi i valori non sono dicts: ovvero il valore del secondo dict sovrascriverà il primo, anche se il valore del secondo dict è Nessuno e il valore del primo è un dict con molte informazioni.

d1 = {'a': {1: 'foo', 2: None}}
d2 = {'a': {1: None, 2: 'bar'}}
d3 = {'b': {3: 'baz'}}
d4 = {'a': {1: 'quux'}}

from collections import MutableMapping

def rec_merge(d1, d2):
    '''
    Update two dicts of dicts recursively, 
    if either mapping has leaves that are non-dicts, 
    the second's leaf overwrites the first's.
    '''
    for k, v in d1.items(): # in Python 2, use .iteritems()!
        if k in d2:
            # this next check is the only difference!
            if all(isinstance(e, MutableMapping) for e in (v, d2[k])):
                d2[k] = rec_merge(v, d2[k])
            # we could further check types and merge as appropriate here.
    d3 = d1.copy()
    d3.update(d2)
    return d3

E adesso

from functools import reduce
reduce(rec_merge, (d1, d2, d3, d4))

ritorna

{'a': {1: 'quux', 2: 'bar'}, 'b': {3: 'baz'}}

Applicazione alla domanda originale:

Ho dovuto rimuovere le parentesi graffe attorno alle lettere e inserirle tra virgolette singole perché questo fosse Python legittimo (altrimenti sarebbero state impostate letteralmente in Python 2.7+) e ho aggiunto una parentesi graffa mancante:

dict1 = {1:{"a":'A'}, 2:{"b":'B'}}
dict2 = {2:{"c":'C'}, 3:{"d":'D'}}

e rec_merge(dict1, dict2)ora ritorna:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}

Che corrisponde al risultato desiderato della domanda iniziale (dopo aver cambiato, ad esempio, la {A}a 'A'.)


10

Basato su @andrew cooke. Questa versione gestisce elenchi nidificati di dadi e consente anche l'opzione di aggiornare i valori

def merge(a, b, path=None, update=True):
    "http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge"
    "merges b into a"
    if path is None: path = []
    for key in b:
        if key in a:
            if isinstance(a[key], dict) and isinstance(b[key], dict):
                merge(a[key], b[key], path + [str(key)])
            elif a[key] == b[key]:
                pass # same leaf value
            elif isinstance(a[key], list) and isinstance(b[key], list):
                for idx, val in enumerate(b[key]):
                    a[key][idx] = merge(a[key][idx], b[key][idx], path + [str(key), str(idx)], update=update)
            elif update:
                a[key] = b[key]
            else:
                raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
        else:
            a[key] = b[key]
    return a

1
Grazie, è così utile. Ho sempre elenchi di dadi nelle mie strutture, le altre soluzioni non riescono a fondere correttamente questo.
SHernandez,

7

Questa semplice procedura ricorsiva unirà un dizionario in un altro sovrascrivendo le chiavi in ​​conflitto:

#!/usr/bin/env python2.7

def merge_dicts(dict1, dict2):
    """ Recursively merges dict2 into dict1 """
    if not isinstance(dict1, dict) or not isinstance(dict2, dict):
        return dict2
    for k in dict2:
        if k in dict1:
            dict1[k] = merge_dicts(dict1[k], dict2[k])
        else:
            dict1[k] = dict2[k]
    return dict1

print (merge_dicts({1:{"a":"A"}, 2:{"b":"B"}}, {2:{"c":"C"}, 3:{"d":"D"}}))
print (merge_dicts({1:{"a":"A"}, 2:{"b":"B"}}, {1:{"a":"A"}, 2:{"b":"C"}}))

Produzione:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
{1: {'a': 'A'}, 2: {'b': 'C'}}

7

Sulla base delle risposte di @andrew cooke. Si occupa in modo migliore degli elenchi nidificati.

def deep_merge_lists(original, incoming):
    """
    Deep merge two lists. Modifies original.
    Recursively call deep merge on each correlated element of list. 
    If item type in both elements are
     a. dict: Call deep_merge_dicts on both values.
     b. list: Recursively call deep_merge_lists on both values.
     c. any other type: Value is overridden.
     d. conflicting types: Value is overridden.

    If length of incoming list is more that of original then extra values are appended.
    """
    common_length = min(len(original), len(incoming))
    for idx in range(common_length):
        if isinstance(original[idx], dict) and isinstance(incoming[idx], dict):
            deep_merge_dicts(original[idx], incoming[idx])

        elif isinstance(original[idx], list) and isinstance(incoming[idx], list):
            deep_merge_lists(original[idx], incoming[idx])

        else:
            original[idx] = incoming[idx]

    for idx in range(common_length, len(incoming)):
        original.append(incoming[idx])


def deep_merge_dicts(original, incoming):
    """
    Deep merge two dictionaries. Modifies original.
    For key conflicts if both values are:
     a. dict: Recursively call deep_merge_dicts on both values.
     b. list: Call deep_merge_lists on both values.
     c. any other type: Value is overridden.
     d. conflicting types: Value is overridden.

    """
    for key in incoming:
        if key in original:
            if isinstance(original[key], dict) and isinstance(incoming[key], dict):
                deep_merge_dicts(original[key], incoming[key])

            elif isinstance(original[key], list) and isinstance(incoming[key], list):
                deep_merge_lists(original[key], incoming[key])

            else:
                original[key] = incoming[key]
        else:
            original[key] = incoming[key]

intuitivo e simmetrico. +1 per la gestione dell'elenco :)
vdwees

6

Se hai un livello sconosciuto di dizionari, suggerirei una funzione ricorsiva:

def combineDicts(dictionary1, dictionary2):
    output = {}
    for item, value in dictionary1.iteritems():
        if dictionary2.has_key(item):
            if isinstance(dictionary2[item], dict):
                output[item] = combineDicts(value, dictionary2.pop(item))
        else:
            output[item] = value
    for item, value in dictionary2.iteritems():
         output[item] = value
    return output

5

Panoramica

Il seguente approccio suddivide il problema di una profonda fusione di cubetti in:

  1. Una funzione di unione superficiale con parametri merge(f)(a,b)che utilizza una funzione fper unire due dadi aeb

  2. Una funzione di fusione ricorsiva fda utilizzare insieme amerge


Implementazione

Una funzione per unire due dadi (non nidificati) può essere scritta in molti modi. Personalmente mi piace

def merge(f):
    def merge(a,b): 
        keys = a.keys() | b.keys()
        return {key:f(a.get(key), b.get(key)) for key in keys}
    return merge

Un buon modo per definire un'appropriata funzione di fusione ricorsiva fconsiste nell'utilizzare il multipledispatch che consente di definire funzioni che valutano lungo percorsi diversi a seconda del tipo di argomenti.

from multipledispatch import dispatch

#for anything that is not a dict return
@dispatch(object, object)
def f(a, b):
    return b if b is not None else a

#for dicts recurse 
@dispatch(dict, dict)
def f(a,b):
    return merge(f)(a,b)

Esempio

Per unire due dadi nidificati è sufficiente usare merge(f)ad esempio:

dict1 = {1:{"a":"A"},2:{"b":"B"}}
dict2 = {2:{"c":"C"},3:{"d":"D"}}
merge(f)(dict1, dict2)
#returns {1: {'a': 'A'}, 2: {'b': 'B', 'c': 'C'}, 3: {'d': 'D'}} 

Appunti:

I vantaggi di questo approccio sono:

  • La funzione è costruita da funzioni più piccole che ciascuna fanno una sola cosa che rende il codice più semplice da ragionare e testare

  • Il comportamento non è codificato ma può essere modificato ed esteso in base alle esigenze, il che migliora il riutilizzo del codice (vedere l'esempio seguente).


Personalizzazione

Alcune risposte hanno anche considerato dadi che contengono elenchi, ad esempio di altri (potenzialmente nidificati). In questo caso, è possibile che si desideri mappare le liste e unirle in base alla posizione. Questo può essere fatto aggiungendo un'altra definizione alla funzione di fusione f:

import itertools
@dispatch(list, list)
def f(a,b):
    return [merge(f)(*arg) for arg in itertools.zip_longest(a, b)]

4

Nel caso qualcuno voglia un altro approccio a questo problema, ecco la mia soluzione.

Virtù : brevi, dichiarative e funzionali nello stile (ricorsivo, non fa mutazione).

Potenziale svantaggio : potrebbe non essere l'unione che stai cercando. Consultare la documentazione per la semantica.

def deep_merge(a, b):
    """
    Merge two values, with `b` taking precedence over `a`.

    Semantics:
    - If either `a` or `b` is not a dictionary, `a` will be returned only if
      `b` is `None`. Otherwise `b` will be returned.
    - If both values are dictionaries, they are merged as follows:
        * Each key that is found only in `a` or only in `b` will be included in
          the output collection with its value intact.
        * For any key in common between `a` and `b`, the corresponding values
          will be merged with the same semantics.
    """
    if not isinstance(a, dict) or not isinstance(b, dict):
        return a if b is None else b
    else:
        # If we're here, both a and b must be dictionaries or subtypes thereof.

        # Compute set of all keys in both dictionaries.
        keys = set(a.keys()) | set(b.keys())

        # Build output dictionary, merging recursively values with common keys,
        # where `None` is used to mean the absence of a value.
        return {
            key: deep_merge(a.get(key), b.get(key))
            for key in keys
        }

Risposta molto interessante, grazie per averla condivisa. Quale sintassi hai usato dopo l'istruzione return? Non ne ho familiarità.
dev_does_software

4

Potresti provare a fondere .


Installazione

$ pip3 install mergedeep

uso

from mergedeep import merge

a = {"keyA": 1}
b = {"keyB": {"sub1": 10}}
c = {"keyB": {"sub2": 20}}

merge(a, b, c) 

print(a)
# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}

Per un elenco completo di opzioni, controlla i documenti !


3

C'è un leggero problema con la risposta di Andrew Cookes: in alcuni casi modifica il secondo argomento bquando si modifica il dict restituito. In particolare è a causa di questa linea:

if key in a:
    ...
else:
    a[key] = b[key]

Se b[key]è un dict, verrà semplicemente assegnato a a, il che significa che eventuali successive modifiche a ciò dictinfluenzeranno sia ae b.

a={}
b={'1':{'2':'b'}}
c={'1':{'3':'c'}}
merge(merge(a,b), c) # {'1': {'3': 'c', '2': 'b'}}
a # {'1': {'3': 'c', '2': 'b'}} (as expected)
b # {'1': {'3': 'c', '2': 'b'}} <----
c # {'1': {'3': 'c'}} (unmodified)

Per risolvere questo problema, la riga dovrebbe essere sostituita con questa:

if isinstance(b[key], dict):
    a[key] = clone_dict(b[key])
else:
    a[key] = b[key]

Dove clone_dictè:

def clone_dict(obj):
    clone = {}
    for key, value in obj.iteritems():
        if isinstance(value, dict):
            clone[key] = clone_dict(value)
        else:
            clone[key] = value
    return

Ancora. Questo ovviamente non tiene conto list, sete altre cose, ma spero che illustri le insidie ​​quando si tenta di uniredicts .

E per completezza, ecco la mia versione, dove puoi passarla più volte dicts:

def merge_dicts(*args):
    def clone_dict(obj):
        clone = {}
        for key, value in obj.iteritems():
            if isinstance(value, dict):
                clone[key] = clone_dict(value)
            else:
                clone[key] = value
        return

    def merge(a, b, path=[]):
        for key in b:
            if key in a:
                if isinstance(a[key], dict) and isinstance(b[key], dict):
                    merge(a[key], b[key], path + [str(key)])
                elif a[key] == b[key]:
                    pass
                else:
                    raise Exception('Conflict at `{path}\''.format(path='.'.join(path + [str(key)])))
            else:
                if isinstance(b[key], dict):
                    a[key] = clone_dict(b[key])
                else:
                    a[key] = b[key]
        return a
    return reduce(merge, args, {})

Perché non deepcopyinvece di clone_dict?
Armando Pérez Marqués,

1
Perché il python stdlib è fantastico e magnifico! Non avevo idea che esistesse - in più era una piccola cosa divertente da programmare :-)
andsens

2

Questa versione della funzione conterà N numero di dizionari e solo dizionari: non è possibile passare parametri impropri o genererà un TypeError. L'unione stessa tiene conto dei conflitti chiave e invece di sovrascrivere i dati da un dizionario più in basso nella catena di unione, crea un insieme di valori e li aggiunge; nessun dato viene perso.

Potrebbe non essere il più efficace sulla pagina, ma è il più completo e non perderai alcuna informazione quando unisci i tuoi 2 a N dicts.

def merge_dicts(*dicts):
    if not reduce(lambda x, y: isinstance(y, dict) and x, dicts, True):
        raise TypeError, "Object in *dicts not of type dict"
    if len(dicts) < 2:
        raise ValueError, "Requires 2 or more dict objects"


    def merge(a, b):
        for d in set(a.keys()).union(b.keys()):
            if d in a and d in b:
                if type(a[d]) == type(b[d]):
                    if not isinstance(a[d], dict):
                        ret = list({a[d], b[d]})
                        if len(ret) == 1: ret = ret[0]
                        yield (d, sorted(ret))
                    else:
                        yield (d, dict(merge(a[d], b[d])))
                else:
                    raise TypeError, "Conflicting key:value type assignment"
            elif d in a:
                yield (d, a[d])
            elif d in b:
                yield (d, b[d])
            else:
                raise KeyError

    return reduce(lambda x, y: dict(merge(x, y)), dicts[1:], dicts[0])

print merge_dicts({1:1,2:{1:2}},{1:2,2:{3:1}},{4:4})

output: {1: [1, 2], 2: {1: 2, 3: 1}, 4: 4}


2

Dal momento che dictviews supporta operazioni sul set, sono stato in grado di semplificare notevolmente la risposta di jterrace.

def merge(dict1, dict2):
    for k in dict1.keys() - dict2.keys():
        yield (k, dict1[k])

    for k in dict2.keys() - dict1.keys():
        yield (k, dict2[k])

    for k in dict1.keys() & dict2.keys():
        yield (k, dict(merge(dict1[k], dict2[k])))

Qualsiasi tentativo di combinare un dict con un non dict (tecnicamente, un oggetto con un metodo "chiavi" e un oggetto senza un metodo "chiavi") genererà un AttributeError. Ciò include sia la chiamata iniziale alla funzione sia le chiamate ricorsive. Questo è esattamente quello che volevo, quindi l'ho lasciato. È possibile catturare facilmente un AttributeErrors generato dalla chiamata ricorsiva e quindi restituire qualsiasi valore desiderato.


2

Short-n-dolce:

from collections.abc import MutableMapping as Map

def nested_update(d, v):
"""
Nested update of dict-like 'd' with dict-like 'v'.
"""

for key in v:
    if key in d and isinstance(d[key], Map) and isinstance(v[key], Map):
        nested_update(d[key], v[key])
    else:
        d[key] = v[key]

Funziona come (ed è basato su) il dict.updatemetodo di Python . Restituisce None(puoi sempre aggiungerlo return dse preferisci) man mano che aggiorna dict dsul posto. Le chiavi vsovrascriveranno tutte le chiavi esistentid (non tenta di interpretare il contenuto del dict).

Funzionerà anche per altri mapping ("dict-like").


1

Il codice dipenderà dalle tue regole per risolvere i conflitti di unione, ovviamente. Ecco una versione che può prendere un numero arbitrario di argomenti e li unisce ricorsivamente a una profondità arbitraria, senza usare alcuna mutazione dell'oggetto. Utilizza le seguenti regole per risolvere i conflitti di unione:

  • i dizionari hanno la precedenza sui valori non-dict (hanno la {"foo": {...}}precedenza su{"foo": "bar"} )
  • argomenti successivi hanno la precedenza su argomenti precedenti (se si uniscono {"a": 1}, {"a", 2}e {"a": 3}in ordine, il risultato sarà {"a": 3})
try:
    from collections import Mapping
except ImportError:
    Mapping = dict

def merge_dicts(*dicts):                                                            
    """                                                                             
    Return a new dictionary that is the result of merging the arguments together.   
    In case of conflicts, later arguments take precedence over earlier arguments.   
    """                                                                             
    updated = {}                                                                    
    # grab all keys                                                                 
    keys = set()                                                                    
    for d in dicts:                                                                 
        keys = keys.union(set(d))                                                   

    for key in keys:                                                                
        values = [d[key] for d in dicts if key in d]                                
        # which ones are mapping types? (aka dict)                                  
        maps = [value for value in values if isinstance(value, Mapping)]            
        if maps:                                                                    
            # if we have any mapping types, call recursively to merge them          
            updated[key] = merge_dicts(*maps)                                       
        else:                                                                       
            # otherwise, just grab the last value we have, since later arguments    
            # take precedence over earlier arguments                                
            updated[key] = values[-1]                                               
    return updated  

1

Avevo due dizionari ( ae b) che potevano contenere un numero qualsiasi di dizionari nidificati. Volevo unirli ricorsivamente, con la bprecedenza a.

Considerando i dizionari nidificati come alberi, quello che volevo era:

  • Aggiornare in amodo che ogni percorso di ogni foglia bvenga rappresentato ina
  • Per sovrascrivere i sottotitoli ase viene trovata una foglia nel percorso corrispondente inb
    • Mantenere l'invariante che tutti i bnodi foglia rimangono foglie.

Le risposte esistenti sono state un po 'complicate per i miei gusti e hanno lasciato alcuni dettagli sullo scaffale. Ho hackerato insieme quanto segue, che supera i test unitari per il mio set di dati.

  def merge_map(a, b):
    if not isinstance(a, dict) or not isinstance(b, dict):
      return b

    for key in b.keys():
      a[key] = merge_map(a[key], b[key]) if key in a else b[key]
    return a

Esempio (formattato per chiarezza):

 a = {
    1 : {'a': 'red', 
         'b': {'blue': 'fish', 'yellow': 'bear' },
         'c': { 'orange': 'dog'},
    },
    2 : {'d': 'green'},
    3: 'e'
  }

  b = {
    1 : {'b': 'white'},
    2 : {'d': 'black'},
    3: 'e'
  }


  >>> merge_map(a, b)
  {1: {'a': 'red', 
       'b': 'white',
       'c': {'orange': 'dog'},},
   2: {'d': 'black'},
   3: 'e'}

I percorsi bche dovevano essere mantenuti erano:

  • 1 -> 'b' -> 'white'
  • 2 -> 'd' -> 'black'
  • 3 -> 'e'.

a aveva i percorsi unici e non contrastanti di:

  • 1 -> 'a' -> 'red'
  • 1 -> 'c' -> 'orange' -> 'dog'

quindi sono ancora rappresentati nella mappa unita.


1

Ho una soluzione iterativa - funziona molto meglio con i dadi grandi e molti di essi (ad esempio jsons ecc.):

import collections


def merge_dict_with_subdicts(dict1: dict, dict2: dict) -> dict:
    """
    similar behaviour to builtin dict.update - but knows how to handle nested dicts
    """
    q = collections.deque([(dict1, dict2)])
    while len(q) > 0:
        d1, d2 = q.pop()
        for k, v in d2.items():
            if k in d1 and isinstance(d1[k], dict) and isinstance(v, dict):
                q.append((d1[k], v))
            else:
                d1[k] = v

    return dict1

notare che questo utilizzerà il valore in d2 per sovrascrivere d1, nel caso in cui non siano entrambi dicts. (uguale a quello di Pythondict.update() )

alcuni test:

def test_deep_update():
    d = dict()
    merge_dict_with_subdicts(d, {"a": 4})
    assert d == {"a": 4}

    new_dict = {
        "b": {
            "c": {
                "d": 6
            }
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d == {
        "a": 4,
        "b": {
            "c": {
                "d": 6
            }
        }
    }

    new_dict = {
        "a": 3,
        "b": {
            "f": 7
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d == {
        "a": 3,
        "b": {
            "c": {
                "d": 6
            },
            "f": 7
        }
    }

    # test a case where one of the dicts has dict as value and the other has something else
    new_dict = {
        'a': {
            'b': 4
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d['a']['b'] == 4

Ho provato con circa ~ 1200 dicts - questo metodo ha richiesto 0,4 secondi, mentre la soluzione ricorsiva ha richiesto ~ 2,5 secondi.


0

Questo dovrebbe aiutare a fondere tutti gli elementi da dict2in dict1:

for item in dict2:
    if item in dict1:
        for leaf in dict2[item]:
            dict1[item][leaf] = dict2[item][leaf]
    else:
        dict1[item] = dict2[item]

Per favore testalo e dicci se questo è quello che volevi.

MODIFICARE:

La soluzione sopra menzionata unisce solo un livello, ma risolve correttamente l'esempio fornito da OP. Per unire più livelli, è necessario utilizzare la ricorsione.


1
Ha una profondità arbitraria di nidificazione
AGF

Che può essere riscritto semplicemente come for k,v in dict2.iteritems(): dict1.setdefault(k,{}).update(v). Ma come ha sottolineato @agf, questo non unisce i dadi nidificati.
Shawn Chin,

@agf: corretto, quindi sembra che l'OP abbia bisogno di una soluzione che ricorre alla ricorrenza. Grazie al fatto che i dizionari sono mutabili, questo dovrebbe essere abbastanza facile da fare. Ma penso che la questione non è sufficientemente specifica da dire che cosa dovrebbe accadere quando arriviamo con posti con diversi livelli di profondità (ad es. Il tentativo di fusione {'a':'b'}con {'a':{'c':'d'}).
Tadeck,

0

Ho testato le tue soluzioni e ho deciso di utilizzare questo nel mio progetto:

def mergedicts(dict1, dict2, conflict, no_conflict):
    for k in set(dict1.keys()).union(dict2.keys()):
        if k in dict1 and k in dict2:
            yield (k, conflict(dict1[k], dict2[k]))
        elif k in dict1:
            yield (k, no_conflict(dict1[k]))
        else:
            yield (k, no_conflict(dict2[k]))

dict1 = {1:{"a":"A"}, 2:{"b":"B"}}
dict2 = {2:{"c":"C"}, 3:{"d":"D"}}

#this helper function allows for recursion and the use of reduce
def f2(x, y):
    return dict(mergedicts(x, y, f2, lambda x: x))

print dict(mergedicts(dict1, dict2, f2, lambda x: x))
print dict(reduce(f2, [dict1, dict2]))

Passare le funzioni come parametri è la chiave per estendere la soluzione jterrace per comportarsi come tutte le altre soluzioni ricorsive.


0

Il modo più semplice a cui riesco a pensare è:

#!/usr/bin/python

from copy import deepcopy
def dict_merge(a, b):
    if not isinstance(b, dict):
        return b
    result = deepcopy(a)
    for k, v in b.iteritems():
        if k in result and isinstance(result[k], dict):
                result[k] = dict_merge(result[k], v)
        else:
            result[k] = deepcopy(v)
    return result

a = {1:{"a":'A'}, 2:{"b":'B'}}
b = {2:{"c":'C'}, 3:{"d":'D'}}

print dict_merge(a,b)

Produzione:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}

0

Ho un'altra soluzione leggermente diversa qui:

def deepMerge(d1, d2, inconflict = lambda v1,v2 : v2) :
''' merge d2 into d1. using inconflict function to resolve the leaf conflicts '''
    for k in d2:
        if k in d1 : 
            if isinstance(d1[k], dict) and isinstance(d2[k], dict) :
                deepMerge(d1[k], d2[k], inconflict)
            elif d1[k] != d2[k] :
                d1[k] = inconflict(d1[k], d2[k])
        else :
            d1[k] = d2[k]
    return d1

Per impostazione predefinita, risolve i conflitti a favore dei valori del secondo dict, ma puoi facilmente ignorarlo, con alcune stregonerie potresti anche essere in grado di buttare fuori delle eccezioni. :).


0
class Utils(object):

    """

    >>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } }
    >>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } }
    >>> Utils.merge_dict(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } }
    True

    >>> main = {'a': {'b': {'test': 'bug'}, 'c': 'C'}}
    >>> suply = {'a': {'b': 2, 'd': 'D', 'c': {'test': 'bug2'}}}
    >>> Utils.merge_dict(main, suply) == {'a': {'b': {'test': 'bug'}, 'c': 'C', 'd': 'D'}}
    True

    """

    @staticmethod
    def merge_dict(main, suply):
        """
        获取融合的字典,以main为主,suply补充,冲突时以main为准
        :return:
        """
        for key, value in suply.items():
            if key in main:
                if isinstance(main[key], dict):
                    if isinstance(value, dict):
                        Utils.merge_dict(main[key], value)
                    else:
                        pass
                else:
                    pass
            else:
                main[key] = value
        return main

if __name__ == '__main__':
    import doctest
    doctest.testmod()

0

ehi lì ho anche avuto lo stesso problema ma ho pensato a una soluzione e la posterò qui, nel caso sia utile anche per altri, fondendo fondamentalmente dizionari nidificati e anche aggiungendo valori, per me avevo bisogno di calcolare alcune probabilità quindi questo uno ha funzionato alla grande:

#used to copy a nested dict to a nested dict
def deepupdate(target, src):
    for k, v in src.items():
        if k in target:
            for k2, v2 in src[k].items():
                if k2 in target[k]:
                    target[k][k2]+=v2
                else:
                    target[k][k2] = v2
        else:
            target[k] = copy.deepcopy(v)

utilizzando il metodo sopra possiamo unire:

target = {'6,6': {'6,63': 1}, '63, 4 ': {' 4,4 ': 1},' 4,4 ': {' 4,3 ': 1} , '6,63': {'63, 4 ': 1}}

src = {'5,4': {'4,4': 1}, '5,5': {'5,4': 1}, '4,4': {'4,3': 1} }

e questo diventerà: {'5,5': {'5,4': 1}, '5,4': {'4,4': 1}, '6,6': {'6,63' : 1}, '63, 4 ': {' 4,4 ': 1},' 4,4 ': {' 4,3 ': 2},' 6,63 ': {'63, 4': 1 }}

nota anche le modifiche qui:

target = {'6,6': {'6,63': 1}, '6,63': {'63, 4 ': 1}, ' 4,4 ': {' 4,3 ': 1} , '63, 4 ': {' 4,4 ': 1}}

src = {'5,4': {'4,4': 1}, '4,3': {'3,4': 1}, '4,4': {'4,9': 1} , '3,4': {'4,4': 1}, '5,5': {'5,4': 1}}

merge = {'5,4': {'4,4': 1}, '4,3': {'3,4': 1}, '6,63': {'63, 4 ': 1} , '5,5': {'5,4': 1}, '6,6': {'6,63': 1}, '3,4': {'4,4': 1}, ' 63,4 ': {' 4,4 ': 1}, ' 4,4 ': {' 4,3 ': 1,' 4,9 ': 1} }

non dimenticare di aggiungere anche l'importazione per la copia:

import copy

0
from collections import defaultdict
from itertools import chain

class DictHelper:

@staticmethod
def merge_dictionaries(*dictionaries, override=True):
    merged_dict = defaultdict(set)
    all_unique_keys = set(chain(*[list(dictionary.keys()) for dictionary in dictionaries]))  # Build a set using all dict keys
    for key in all_unique_keys:
        keys_value_type = list(set(filter(lambda obj_type: obj_type != type(None), [type(dictionary.get(key, None)) for dictionary in dictionaries])))
        # Establish the object type for each key, return None if key is not present in dict and remove None from final result
        if len(keys_value_type) != 1:
            raise Exception("Different objects type for same key: {keys_value_type}".format(keys_value_type=keys_value_type))

        if keys_value_type[0] == list:
            values = list(chain(*[dictionary.get(key, []) for dictionary in dictionaries]))  # Extract the value for each key
            merged_dict[key].update(values)

        elif keys_value_type[0] == dict:
            # Extract all dictionaries by key and enter in recursion
            dicts_to_merge = list(filter(lambda obj: obj != None, [dictionary.get(key, None) for dictionary in dictionaries]))
            merged_dict[key] = DictHelper.merge_dictionaries(*dicts_to_merge)

        else:
            # if override => get value from last dictionary else make a list of all values
            values = list(filter(lambda obj: obj != None, [dictionary.get(key, None) for dictionary in dictionaries]))
            merged_dict[key] = values[-1] if override else values

    return dict(merged_dict)



if __name__ == '__main__':
  d1 = {'aaaaaaaaa': ['to short', 'to long'], 'bbbbb': ['to short', 'to long'], "cccccc": ["the is a test"]}
  d2 = {'aaaaaaaaa': ['field is not a bool'], 'bbbbb': ['field is not a bool']}
  d3 = {'aaaaaaaaa': ['filed is not a string', "to short"], 'bbbbb': ['field is not an integer']}
  print(DictHelper.merge_dictionaries(d1, d2, d3))

  d4 = {"a": {"x": 1, "y": 2, "z": 3, "d": {"x1": 10}}}
  d5 = {"a": {"x": 10, "y": 20, "d": {"x2": 20}}}
  print(DictHelper.merge_dictionaries(d4, d5))

Produzione:

{'bbbbb': {'to long', 'field is not an integer', 'to short', 'field is not a bool'}, 
'aaaaaaaaa': {'to long', 'to short', 'filed is not a string', 'field is not a bool'}, 
'cccccc': {'the is a test'}}

{'a': {'y': 20, 'd': {'x1': 10, 'x2': 20}, 'z': 3, 'x': 10}}

Mentre questo codice può rispondere alla domanda, fornendo ulteriore contesto riguardo al perché e / o al modo in cui questo codice risponde alla domanda migliora il suo valore a lungo termine.
xiawi,

Penso che questa sia un'implementazione generica di fusione di uno o più dizionari nidificati tenendo in considerazione il tipo di oggetti che verranno marchiati
Dorcioman,

0

dai un'occhiata al toolzpacchetto

import toolz
dict1={1:{"a":"A"},2:{"b":"B"}}
dict2={2:{"c":"C"},3:{"d":"D"}}
toolz.merge_with(toolz.merge,dict1,dict2)

{1: {'a': 'A'}, 2: {'b': 'B', 'c': 'C'}, 3: {'d': 'D'}}

0

La seguente funzione unisce b in a.

def mergedicts(a, b):
    for key in b:
        if isinstance(a.get(key), dict) or isinstance(b.get(key), dict):
            mergedicts(a[key], b[key])
        else:
            a[key] = b[key]
    return a

0

E solo un'altra leggera variazione:

Ecco una pura funzione di aggiornamento profondo basata su set python3. Aggiorna i dizionari nidificati eseguendo il ciclo attraverso un livello alla volta e si chiama per aggiornare ogni livello successivo dei valori del dizionario:

def deep_update(dict_original, dict_update):
    if isinstance(dict_original, dict) and isinstance(dict_update, dict):
        output=dict(dict_original)
        keys_original=set(dict_original.keys())
        keys_update=set(dict_update.keys())
        similar_keys=keys_original.intersection(keys_update)
        similar_dict={key:deep_update(dict_original[key], dict_update[key]) for key in similar_keys}
        new_keys=keys_update.difference(keys_original)
        new_dict={key:dict_update[key] for key in new_keys}
        output.update(similar_dict)
        output.update(new_dict)
        return output
    else:
        return dict_update

Un semplice esempio:

x={'a':{'b':{'c':1, 'd':1}}}
y={'a':{'b':{'d':2, 'e':2}}, 'f':2}

print(deep_update(x, y))
>>> {'a': {'b': {'c': 1, 'd': 2, 'e': 2}}, 'f': 2}

0

Che ne dici di un'altra risposta?!? Questo evita anche le mutazioni / effetti collaterali:

def merge(dict1, dict2):
    output = {}

    # adds keys from `dict1` if they do not exist in `dict2` and vice-versa
    intersection = {**dict2, **dict1}

    for k_intersect, v_intersect in intersection.items():
        if k_intersect not in dict1:
            v_dict2 = dict2[k_intersect]
            output[k_intersect] = v_dict2

        elif k_intersect not in dict2:
            output[k_intersect] = v_intersect

        elif isinstance(v_intersect, dict):
            v_dict2 = dict2[k_intersect]
            output[k_intersect] = merge(v_intersect, v_dict2)

        else:
            output[k_intersect] = v_intersect

    return output
dict1 = {1:{"a":{"A"}}, 2:{"b":{"B"}}}
dict2 = {2:{"c":{"C"}}, 3:{"d":{"D"}}}
dict3 = {1:{"a":{"A"}}, 2:{"b":{"B"},"c":{"C"}}, 3:{"d":{"D"}}}

assert dict3 == merge(dict1, dict2)
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.