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

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

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.

  • Как проверить отправителя входящего адреса электронной почты в Google App Engine?
  • Использование типов классов в python
  • Как получить доступ к значению ключа словаря, присутствующего в списке?
  • Переменная проверка, что-то не правильное
  • Питон не работает
  • Невозможно изменить класс экземпляра frontend в приложении appengine.
  • Как программно определить, является ли свойство ndb многозначным
  • Как преобразовать числовую строку с запятыми значения места в целое число?
  • 16 Solutions collect form web for “Словари словарей объединяются”

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

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

     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 вы можете назвать его как merge(dict(a), b) .

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

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

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

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

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

    Один вопрос с этим вопросом заключается в том, что значения 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 путем добавления отсутствующих ключей и обновления существующих ключей

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

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

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

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

     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 

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

     def merge (a, b, path = None, update = True):
         "Http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge"
         "сливает b в"
         если путь равен None: path = []
         для ключа в b:
             если ключ в:
                 if isststance (a [key], dict) и isststance (b [key], dict):
                     merge (a [key], b [key], path + [str (ключ)])
                 elif a [ключ] == b [ключ]:
                     pass # то же значение листа
                 elif isinstance ([ключ], список) и isststance (b [ключ], список):
                     для idx, val в перечислении (b [ключ]):
                         a [key] [idx] = merge (a [key] [idx], b [key] [idx], path + [str (key), str (idx)], update = update)
                 Обновление elif:
                     a [ключ] = b [ключ]
                 еще:
                     raise («Конфликт в% s»% ».). join (путь + [str (ключ)]))
             еще:
                 a [ключ] = b [ключ]
         вернуть
    

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

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

    Простейший случай: «листья – это вложенные дикты, которые заканчиваются пустым диктофоном»:

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

    И сейчас

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

    В этой версии функции будет учитываться 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}) 

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

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

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

    РЕДАКТИРОВАТЬ:

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

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

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

    Есть небольшая проблема с ответом на 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, {}) 

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

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

    • Чтобы обновить a чтобы каждый путь к каждому элементу в b был представлен в
    • Чтобы переписать поддеревья 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'

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

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

    • словари имеют приоритет над недиктическими значениями ( {"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 

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

     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, чтобы вести себя как все другие рекурсивные решения.

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

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

    Поскольку 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. Это включает как начальный вызов функции, так и рекурсивные вызовы. Это именно то, что я хотел, поэтому я его оставил. Вы можете легко поймать атрибуты, вызванные рекурсивным вызовом, а затем предоставить любое значение, которое вам нужно.

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

     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, но вы можете легко переопределить это, с помощью какого-то колдовства вы можете даже выбросить из него исключения. :).

    Python - лучший язык программирования в мире.