Я не уверен, как я мог бы легко это сделать с помощью Python.
Переведено автоматически
Ответ 1
На самом деле это довольно сложно - особенно если вам нужно полезное сообщение об ошибке, когда что-то несовместимо, при этом корректно принимая повторяющиеся, но согласованные записи (чего не делает никакой другой ответ здесь ..)
Предполагая, что у вас нет огромного количества записей, проще всего использовать рекурсивную функцию:
defmerge(a: dict, b: dict, path=[]): for key in b: if key in a: ifisinstance(a[key], dict) andisinstance(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"
Вот простой способ сделать это с помощью генераторов:
defmergedicts(dict1, dict2): for k inset(dict1.keys()).union(dict2.keys()): if k in dict1 and k in dict2: ifisinstance(dict1[k], dict) andisinstance(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])
Одна из проблем с этим вопросом заключается в том, что значениями dict могут быть произвольно сложные фрагменты данных. На основе этих и других ответов я разработал этот код:
classYamlReaderError(Exception): pass
defdata_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 isNoneorisinstance(a, str) orisinstance(a, unicode) orisinstance(a, int) orisinstance(a, long) orisinstance(a, float): # border case for first run or if a is a primitive a = b elifisinstance(a, list): # lists can be only appended ifisinstance(b, list): # merge lists a.extend(b) else: # append to list a.append(b) elifisinstance(a, dict): # dicts must be merged ifisinstance(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 путем добавления отсутствующих ключей и обновления существующих ключей