Словари слияния словарей
Поскольку это канонический вопрос (несмотря на некоторые необщности), я даю канонический подход Pythonic к решению этой проблемы.
Простейший случай: «листья - это вложенные дикты, заканчивающиеся пустыми»:
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': {}}}}
Сложный кейс: «листья любого другого типа:»
Так что, если они заканчиваются диктатами, это простой случай слияния конечных пустых диктов. Если нет, то это не так тривиально. Если строки, как вы их объединить? Наборы можно обновлять аналогичным образом, чтобы мы могли выполнить эту обработку, но мы теряем порядок, в котором они были объединены. Так имеет ли значение порядок?
Таким образом, вместо получения дополнительной информации, простейший подход состоит в том, чтобы дать им стандартную обработку обновления, если оба значения не являются диктатами: т.е. значение второго диктанта будет перезаписывать первое, даже если значение второго диктанта равно None, а значение первого значение - это диктат с большим количеством информации.
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'
.)