Вопрос-Ответ

Deep merge dictionaries of dictionaries in Python

Словари глубокого слияния словарей на Python

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

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.

Переведено автоматически
Ответ 1

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

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

def merge(a: dict, b: dict, 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]:
raise Exception('Conflict at ' + '.'.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 , и в этом случае вы можете использовать:

from functools import reduce
reduce(merge, [dict1, dict2, dict3...])

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

Примечание: я отредактировал свой первоначальный ответ, чтобы изменить первый аргумент; это упрощает объяснение "reduce"

Ответ 2

Вы могли бы попробовать mergedeep.


Установка

$ pip3 install mergedeep

Использование

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

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


Ответ 3

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

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'}}
Ответ 4

Одна из проблем с этим вопросом заключается в том, что значениями 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, где мне приходится иметь дело только с подмножеством возможных типов данных. Следовательно, я могу игнорировать кортежи и другие объекты. Для меня разумная логика слияния означает


  • замена скаляров

  • добавлять списки

  • объединяет dicts путем добавления отсутствующих ключей и обновления существующих ключей

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

python dictionary