Подтвердить что ты не робот

Словари словарей объединяются

Мне нужно объединить несколько словарей, вот что я имею, например:

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

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

Поскольку A B C и D являются листьями дерева, например {"info1":"value", "info2":"value2"}

Существует неизвестный уровень (глубина) словарей, это может быть {2:{"c":{"z":{"y":{C}}}}}

В моем случае он представляет структуру каталога/файлов с узлами, являющимися документами, и оставляет файлы.

Я хочу объединить их, чтобы получить:

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

Я не уверен, как легко это сделать с Python.

4b9b3361

Ответ 1

это на самом деле довольно сложно - особенно если вы хотите получить полезное сообщение об ошибке, когда что-то противоречиво, при правильном принятии повторяющихся, но непротиворечивых записей (что-то не дает другого ответа здесь).

Предполагая, что у вас нет огромного количества записей, рекурсивная функция проще всего:

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

обратите внимание, что это мутирует a - содержимое b добавляется к a (которое также возвращается). если вы хотите сохранить a, вы можете назвать его как merge(dict(a), b).

agf указал (ниже), что у вас может быть более двух dicts, и в этом случае вы можете использовать:

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

где все будет добавлено в dict1.

[note - я отредактировал свой первоначальный ответ, чтобы изменить первый аргумент; что упрощает объяснение "уменьшить" ]

ps в python 3, вам также понадобится from functools import reduce

Ответ 2

Здесь простой способ сделать это с помощью генераторов:

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

Отпечатки:

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

Ответ 3

Один вопрос с этим вопросом заключается в том, что значения dict могут быть произвольно сложными частями данных. Основываясь на этих и других ответах, я придумал этот код:

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

Мой вариант использования слияние файлов YAML, где мне нужно иметь дело только с подмножеством возможных типов данных. Поэтому я могу игнорировать кортежи и другие объекты. Для меня разумная логика слияния означает

  • заменить скаляры
  • добавить списки
  • merge dicts путем добавления отсутствующих ключей и обновления существующих ключей

Все остальное и непредвиденные обстоятельства приводят к ошибке.

Ответ 4

Словари словарей объединяются

Так как это канонический вопрос (несмотря на некоторые не общие), я предоставляю канонический подход к решению этой проблемы.

Простейший случай: "листья - вложенные dicts, которые заканчиваются пустым dicts":

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

Это самый простой случай для рекурсии, и я бы рекомендовал два наивных подхода:

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

Я считаю, что я предпочел бы второе, но помню, что первоначальное состояние первого должно было быть перестроено из его происхождения. Здесь использование:

>>> 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': {}}}}

Комплексный случай: "листья имеют любой другой тип:"

Итак, если они заканчиваются на dicts, это простой случай слияния конца пустой dicts. Если нет, это не так тривиально. Если строки, как вы их объединяете? Наборы можно обновлять аналогичным образом, поэтому мы можем дать это лечение, но мы теряем порядок, в котором они были объединены. Значит, имеет значение порядок?

Таким образом, вместо большей информации простейший подход будет заключаться в том, чтобы дать им стандартную обработку обновления, если оба значения не являются dicts: то есть второе значение dict будет перезаписывать первое, даже если второе значение dict - None и первое Значение - это dict с большим количеством информации.

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

А теперь

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

возвращает

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

Заявка на исходный вопрос:

Мне пришлось удалить фигурные скобки вокруг букв и поместить их в одинарные кавычки, чтобы это было законным Python (иначе они были бы установлены литералы в Python 2.7+), а также добавили отсутствующую фигуру:

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

и rec_merge(dict1, dict2) теперь возвращает:

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

Что соответствует желаемому результату исходного вопроса (после изменения, например, от {A} до 'A'.)

Ответ 5

На основе @andrew cooke. Эта версия обрабатывает вложенные списки dicts, а также позволяет обновлять значения

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

Ответ 6

Если у вас неизвестный уровень словарей, я бы предложил рекурсивную функцию:

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

Ответ 7

Основано на ответах @andrew cooke. Он лучше обрабатывает вложенные списки.

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]

Ответ 8

Эта простая рекурсивная процедура объединит один словарь в другой, переопределяя конфликтующие ключи:

#!/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"}}))

Вывод:

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

Ответ 9

обзор

Следующий подход подразделяет проблему глубокого слияния dicts на:

  1. Параметризированная функция merge(f)(a,b) которая использует функцию f для слияния двух dicts a и b

  2. Рекурсивная функция слияния f которая будет использоваться вместе с merge


Реализация

Функция для слияния двух (не вложенных) dicts может быть написана многими способами. Мне лично нравится

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

Хорошим способом определения соответствующей повторяющейся функции слияния f является использование multipledispatch, которое позволяет определять функции, которые оцениваются по различным путям в зависимости от типа их аргументов.

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)

пример

Чтобы слить два вложенных dicts, просто используйте merge(f) например:

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

Заметки:

Преимущества такого подхода:

  • Функция построена из меньших функций, каждая из которых делает одну вещь, которая делает код более простым для объяснения и проверки

  • Поведение не является жестко запрограммированным, но может быть изменено и расширено по мере необходимости, что улучшает повторное использование кода (см. Пример ниже).


настройка

В некоторых ответах также рассматривались диктофоны, содержащие списки, например, других (потенциально вложенных) dicts. В этом случае может потребоваться отображение над списками и объединение их в зависимости от позиции. Это можно сделать, добавив другое определение функции слияния f:

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

Ответ 10

Есть небольшая проблема с cookie andrew cookes: В некоторых случаях он изменяет второй аргумент b при изменении возвращаемого dict. В частности, это из-за этой строки:

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

Если b[key] является dict, ему просто присваивается a, то есть любые последующие изменения этого dict будут влиять как на a, так и на 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)

Чтобы исправить это, строка должна быть заменена следующим:

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

Где 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

Тем не менее. Это явно не учитывает list, set и другие вещи, но я надеюсь, что это иллюстрирует подводные камни при попытке объединить dicts.

И для полноты, вот моя версия, где вы можете передать ей несколько 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, {})

Ответ 11

В этой версии функции будет учтено N количество словарей, и только словари - не могут быть переданы никакие неправильные параметры, или это вызовет TypeError. Сама слияние учитывает ключевые конфликты, и вместо того, чтобы переписывать данные из словаря дальше по цепочке слияния, он создает набор значений и добавляет к этому; данные не теряются.

Он может быть не самым эффективным на странице, но он наиболее тщательный, и вы не потеряете какую-либо информацию, когда вы объедините свои 2 в 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})

вывод: {1: [1, 2], 2: {1: 2, 3: 1}, 4: 4}

Ответ 12

Так как dictviews поддерживает операции набора, мне удалось значительно упростить ответ 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])))

Любая попытка объединить dict с не-dict (технически, объект с методом "keys" и объект без метода "keys" ) вызовет AttributeError. Это включает как начальный вызов функции, так и рекурсивные вызовы. Это именно то, что я хотел, поэтому я его оставил. Вы можете легко поймать атрибуты, вызванные рекурсивным вызовом, а затем предоставить любое значение, которое вам нравится.

Ответ 13

Это должно помочь слить все элементы из dict2 в 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]

Пожалуйста, проверьте его и сообщите нам, действительно ли это то, что вы хотели.

EDIT:

Вышеупомянутое решение объединяет только один уровень, но правильно решает пример, заданный OP. Чтобы объединить несколько уровней, следует использовать рекурсию.

Ответ 14

У меня было два словаря (a и b), каждый из которых мог содержать любое количество вложенных словарей. Я хотел рекурсивно объединить их, причем b имеет приоритет над a.

Учитывая вложенные словари как деревья, я хотел:

  • Чтобы обновить a, чтобы каждый путь к каждому листу в b был представлен в a
  • Чтобы заменить поддеревья a, если лист найден в соответствующем пути в b
    • Поддерживайте инвариант, что все b листовые узлы остаются листьями.

Существующие ответы были немного сложными по моему вкусу и оставили некоторые детали на полке. Я взломал следующее, которое проходит модульные тесты для моего набора данных.

  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

Пример (отформатирован для ясности):

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

Пути в b, которые необходимо было сохранить, были следующими:

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

a имели уникальные и не конфликтующие пути:

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

чтобы они все еще были представлены на объединенной карте.

Ответ 15

Short-н-сладкий:

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]

Это работает как (и на основе) метод Python dict.update. Он возвращает None (вы всегда можете добавить return d если хотите), поскольку он обновляет dict d на месте. Ключи v будут перезаписывать любые существующие ключи в d (он не пытается интерпретировать содержимое dict).

Он также будет работать для других ("диктоподобных") отображений.

Ответ 16

В случае, если кто-то хочет еще один подход к этой проблеме, вот мое решение.

Добродетели: короткие, декларативные и функциональные по стилю (рекурсивные, без мутаций).

Потенциальный недостаток: это может быть не то слияние, которое вы ищете. Обратитесь к документации для семантики.

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
        }

Ответ 17

Вы можете попробовать mergedeep.


Установка

$ pip3 install mergedeep

Usage

Usage
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}}

Полный список параметров можно найти в документации!

Ответ 18

Конечно, код будет зависеть от ваших правил для разрешения конфликтов слияния. Здесь есть версия, которая может принимать произвольное количество аргументов и рекурсивно переводить их на произвольную глубину без использования какой-либо мутации объекта. Для разрешения конфликтов слияния используются следующие правила:

  • словари имеют приоритет над недиктическими значениями ({"foo": {...}} имеет приоритет над {"foo": "bar"})
  • более поздние аргументы имеют приоритет перед более ранними аргументами (если вы объедините {"a": 1}, {"a", 2} и {"a": 3} в порядке, результат будет {"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  

Ответ 19

Я тестировал ваши решения и решил использовать это в своем проекте:

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

Передача функций в качестве параметров является ключом к расширению решения jterrace, чтобы вести себя как все другие рекурсивные решения.

Ответ 20

Самый простой способ, о котором я могу думать:

#!/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)

Вывод:

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

Ответ 21

У меня есть другое немного другое решение:

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

По умолчанию он разрешает конфликты в пользу значений из второго dict, но вы можете легко переопределить это, с помощью какого-то колдовства вы можете даже выбросить из него исключения.:.)

Ответ 22

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

Ответ 23

эй, там у меня тоже была такая же проблема, но я, хотя и из решения, и я отправлю ее здесь, в случае, если она также полезна для других, в основном слияние вложенных словарей, а также добавление значений, для меня мне нужно было вычислить некоторые вероятности, чтобы это один работал отлично:

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

используя вышеуказанный метод, мы можем объединить:

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

и это станет: {'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 }}

также обратите внимание на изменения здесь:

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

не забудьте также добавить импорт для копии:

import copy

Ответ 24

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

Выход:

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