사전의 사전을 병합하는 방법?


129

여러 사전을 병합해야합니다. 예를 들어 다음과 같습니다.

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

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

A B CD나무의 잎이있는 것 같아{"info1":"value", "info2":"value2"}

사전의 알 수없는 수준 (깊이)이있을 수 있습니다. {2:{"c":{"z":{"y":{C}}}}}

내 경우에는 노드가 문서이고 파일이있는 디렉토리 / 파일 구조를 나타냅니다.

나는 그들을 얻기 위해 병합하고 싶다 :

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

파이썬으로 어떻게 쉽게 할 수 있는지 잘 모르겠습니다.


임의의 사전 깊이로 무엇을 원하십니까? 당신 yc레벨 또는 무엇 을 평평하게 하시겠습니까? 귀하의 예가 불완전합니다.
agf

내 NestedDict 클래스를 확인하십시오 : stackoverflow.com/a/16296144/2334951 병합 등의 중첩 된 사전 구조를 관리합니다.
SzieberthAdam

3
해결책을 찾는 모든 사람에게 경고 :이 질문은 중첩 된 dicts에 관한 것입니다. 대부분의 답변은 구조 내에서 복잡한 목록의 dict 목록을 올바르게 처리하지 못합니다. : 당신은이 시도를 아래 @Osiloke의 대답이 필요한 경우 stackoverflow.com/a/25270947/1431660
SHernandez


답변:


143

이것은 실제로 까다로울 수 있습니다-특히 일이 일치하지 않을 때 유용한 오류 메시지를 원할 때 복제하지만 일관된 항목을 올바르게 수락합니다 (여기서 다른 대답은 없습니다 ...)

많은 수의 항목이 없다고 가정하면 재귀 함수가 가장 쉽습니다.

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에 추가됩니다.

[참고-첫 번째 인수를 변경하기 위해 초기 답변을 편집했습니다. "감소"를 설명하기 쉽게 만듭니다.]

파이썬 3의 ps도 필요합니다. from functools import reduce


1
그런 다음 이것을 reduce등가 루프 안에 붙여 dict두 개가 아닌 임의의 수의 s 로 작업 할 수 있습니다. 그러나, 나는이 그가 (그는 분명하지 않다) 중 원하는 것을하지 모르겠어요, 당신이와 끝까지 2: {'c': {'z': {'y': {'info1': 'value', 'info2': 'value2'}}}, 'b': {'info1': 'value', 'info2': 'value2'}}자신의 두 번째 예를 들어, 내가하지 않도록 자신이 원하는 여부 해요 zy최대 평평하거나하지?
agf

1
그것들은 디렉토리 구조이므로 평평하지 않은 것을 원하십니까? 죄송합니다, "다중 사전"을 놓쳤습니다. 예, 줄이면 좋을 것입니다. 그것을 추가 할 것입니다.
앤드류 쿡

이것은 내가 원하는 것을 정확하게합니다! 나는 충분히 명확하지 않아서 미안하다 ... 나는 파이썬으로 괜찮다고 생각했다. : // 중첩 된 dicts 때문에 재귀 함수가 필요했다. 비록 그것이 감소와 함께 작동하게 할 수있는 것 같습니다 ...
fdhex

2
dicts 아래에 최종 중첩 수준으로 목록이있는 사람은 두 목록을 연결하는 오류를 발생시키는 대신이 작업을 수행 할 수 있습니다 a[key] = a[key] + b[key]. 유용한 답변에 감사드립니다.
kevinmicke

1
> a를 유지하려면 merge (dict (a), b)와 같이 호출 할 수 있습니다. 중첩 된 dicts는 여전히 변경됩니다. 이를 피하려면을 사용하십시오 copy.deepcopy.
rcorre

30

다음은 생성기를 사용하여 쉽게 수행 할 수있는 방법입니다.

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

생성기 테마를 유지하려면 chain (dict1.keys (), dict2.keys ())
andrew cooke

중복 키를 얻지 않습니까?
jterrace

이것은 적어도 내 데이터 세트에서 작업을하는 것처럼 보이지만 수율과 생성기를 잘 이해하지 못했기 때문에 나는 그 이유에 대해 거의 잃어 버렸지 만 조금 더 열심히 시도하면 유용 할 것입니다!
fdhex

아, 예, 중복 키를 얻습니다. 여전히 세트로 포장해야합니다. 죄송합니다.
Andrew cooke

2
나는 이것이 특별히 도움이되었다는 것을 알았다. 그러나 멋진 점은 함수가 충돌을 매개 변수로 해결하도록하는 것입니다.
mentatkgs

25

이 질문의 한 가지 문제는 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 파일을 병합 하는 것입니다. 따라서 튜플 및 기타 객체를 무시할 수 있습니다. 나에게 합리적인 합병 논리 수단

  • 스칼라 교체
  • 목록 추가
  • 누락 된 키를 추가하고 기존 키를 업데이트하여 병합 받아쓰기

다른 모든 항목과 예상치 못한 결과는 오류를 발생시킵니다.


1
환상적인. json 덤프에서도 잘 작동합니다. 오류 처리를 제거했습니다. (게으른, json을 위해 적절한 일을 할 수 있습니다.)
dgBP

3
"isinstance"시퀀스는 교체 할 수 isinstance(a, (str, unicode, int, long, float))있습니까?
simahawk

12

사전의 사전 병합

이것은 (일반적이지 않더라도) 정식 질문 이므로이 문제를 해결하기 위해 정식 Pythonic 접근법을 제공하고 있습니다.

가장 간단한 경우 : "잎은 빈 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를 끝내는 간단한 경우입니다. 그렇지 않다면 그렇게 사소한 것이 아닙니다. 문자열 인 경우 어떻게 병합합니까? 세트도 비슷하게 업데이트 할 수 있으므로 처리를 할 수는 있지만 병합 된 순서를 잃게됩니다. 주문이 중요합니까?

따라서 더 많은 정보 대신에 가장 간단한 방법은 두 값이 모두 dict가 아닌 경우 표준 업데이트 처리를 제공하는 것입니다. 즉 두 번째 dict 값이 None이고 첫 번째 값이 a 인 경우에도 두 번째 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 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'.로 변경 한 후 )


10

@andrew cooke을 기반으로합니다. 이 버전은 중첩 된 dict 목록을 처리하고 값을 업데이트하는 옵션도 허용합니다.

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

1
고마워, 이것은 매우 도움이됩니다. 나는 항상 내 구조에 dicts 목록을 가지고 있는데 다른 솔루션은 이것을 올바르게 병합 할 수 없습니다.
SHernandez

7

이 간단한 재귀 절차는 충돌하는 키를 재정의하면서 한 사전을 다른 사전으로 병합합니다.

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

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]

직관적이고 대칭 적입니다. :) 목록 처리를위한 +1
vdwees

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

5

개요

다음 접근법은 dicts의 심층 병합 문제를 세분화합니다.

  1. 매개 변수화 얕은 병합 기능 merge(f)(a,b)함수를 사용하여 f두 dicts 병합 ab

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

노트:

이 방법의 장점은 다음과 같습니다.

  • 이 함수는 각각 하나의 일을하는 더 작은 함수에서 빌드되므로 코드를 추론하고 테스트하기가 더 간단 해집니다.

  • 이 동작은 하드 코딩되지 않지만 필요에 따라 변경 및 확장되어 코드 재사용이 향상됩니다 (아래 예 참조).


커스터마이징

또한 일부 답변은 다른 (잠재적으로 중첩 된) dict 목록을 포함하는 dict을 고려했습니다. 이 경우 목록을 매핑하고 위치를 기준으로 병합 할 수 있습니다. 병합 함수에 다른 정의를 추가하여 수행 할 수 있습니다 f.

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

4

누군가이 문제에 대한 또 다른 접근법을 원한다면 여기에 내 해결책이 있습니다.

미덕 : 짧고 선언적이며 기능적인 스타일 (재귀 적, 돌연변이 없음).

잠재적 인 단점 : 찾고있는 병합이 아닐 수도 있습니다. 시맨틱은 docstring을 참조하십시오.

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
        }

매우 흥미로운 답변입니다. 공유해 주셔서 감사합니다. return 문 뒤에 어떤 구문을 사용 했습니까? 익숙하지 않습니다.
dev_does_software 11

4

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

앤드류 쿡스 답변에 약간의 문제가 있습니다. 어떤 경우 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, {})

deepcopy대신에 clone_dict?
Armando Pérez Marqués

1
파이썬 stdlib가 거대하고 훌륭하기 때문에! 나는 이것이 존재하는 단서가 없었다 – 게다가 그것은 코딩하기에 재미있는 작은 일이었다 :-)
andsens

2

이 버전의 함수는 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}


2

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를 비 dict (기술적으로는 'keys'메서드가있는 객체와 'keys'메서드가없는 객체)와 결합하려고하면 AttributeError가 발생합니다. 여기에는 함수에 대한 초기 호출과 재귀 호출이 모두 포함됩니다. 이것은 내가 원하는 것이므로 그대로 두었습니다. 재귀 호출에 의해 발생하는 AttributeError를 쉽게 포착 한 다음 원하는 값을 얻을 수 있습니다.


2

짧고 달콤한 :

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]

이것은 파이썬의 dict.update방법 과 같은 방식으로 작동합니다 . 그것은 dict 를 제자리에 업데이트 할 None때 반환합니다 ( return d원하는 경우 언제든지 추가 할 수 있음) d. 키는 v기존 키를 덮어 씁니다.d (dict의 내용을 해석하지는 않습니다).

다른 ( "dict-like") 매핑에도 작동합니다.


1

코드는 물론 병합 충돌을 해결하기위한 규칙에 따라 다릅니다. 다음은 임의의 수의 인수를 사용하여 객체 돌연변이를 사용하지 않고 임의의 깊이로 재귀 적으로 병합 할 수있는 버전입니다. 다음 규칙을 사용하여 병합 충돌을 해결합니다.

  • 딕셔너리는 비 -dict 값 {"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  

1

나는 두 개의 사전 ( ab)을 가지고 있는데, 각각에는 여러 개의 사전이 포함될 수 있습니다. 나는 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'

병합 된 맵에 여전히 표시됩니다.


1

나는 반복적 인 해결책을 가지고 있습니다-큰 dicts와 많은 것들 (예 : jsons 등)로 훨씬 잘 작동합니다.

import collections


def merge_dict_with_subdicts(dict1: dict, dict2: dict) -> dict:
    """
    similar behaviour to builtin dict.update - but knows how to handle nested dicts
    """
    q = collections.deque([(dict1, dict2)])
    while len(q) > 0:
        d1, d2 = q.pop()
        for k, v in d2.items():
            if k in d1 and isinstance(d1[k], dict) and isinstance(v, dict):
                q.append((d1[k], v))
            else:
                d1[k] = v

    return dict1

이것은 둘 다 dict가 아닌 경우 d2의 값을 사용하여 d1을 대체합니다. (파이썬과 동일dict.update() )

몇 가지 테스트 :

def test_deep_update():
    d = dict()
    merge_dict_with_subdicts(d, {"a": 4})
    assert d == {"a": 4}

    new_dict = {
        "b": {
            "c": {
                "d": 6
            }
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d == {
        "a": 4,
        "b": {
            "c": {
                "d": 6
            }
        }
    }

    new_dict = {
        "a": 3,
        "b": {
            "f": 7
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d == {
        "a": 3,
        "b": {
            "c": {
                "d": 6
            },
            "f": 7
        }
    }

    # test a case where one of the dicts has dict as value and the other has something else
    new_dict = {
        'a': {
            'b': 4
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d['a']['b'] == 4

약 1200 dicts로 테스트했습니다.이 방법은 0.4 초가 걸리고 재귀 솔루션은 ~ 2.5 초가 걸렸습니다.


0

이렇게하면 모든 항목 dict2dict1다음 항목으로 병합하는 데 도움이 됩니다 .

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에서 제공 한 예제를 올바르게 해결합니다. 여러 수준을 병합하려면 재귀를 사용해야합니다.


1
그는 임의의 중첩 깊이를
가졌다

간단히로 다시 쓸 수 있습니다 for k,v in dict2.iteritems(): dict1.setdefault(k,{}).update(v). 그러나 @agf가 지적했듯이 이것은 중첩 된 dicts를 병합하지 않습니다.
Shawn Chin

@agf : 맞습니다. 따라서 OP에는 재귀를 사용하는 솔루션이 필요한 것 같습니다. 사전이 변경 가능하다는 사실 덕분에이 작업은 매우 쉽게 수행 할 수 있습니다. 그러나 질문은 깊이가 다른 장소 (예 : {'a':'b'}와 병합하려고하는 곳)를 생각해 낼 때 어떤 일이 발생 해야하는지 알 수있을만큼 구체적이지 않다고 생각합니다 {'a':{'c':'d'}.
Tadeck

0

귀하의 솔루션을 테스트하고 내 프로젝트 에서이 솔루션을 사용하기로 결정했습니다.

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 솔루션을 다른 모든 재귀 솔루션으로 작동하도록 확장하는 데 중요합니다.


0

내가 생각할 수있는 가장 쉬운 방법은 다음과 같습니다.

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

0

여기에 약간 다른 해결책이 있습니다.

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의 값을 선호하여 충돌을 해결하지만 쉽게 무시할 수 있습니다. 심술쟁이로 예외를 던질 수도 있습니다. :).


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

0

이봐, 나도 같은 문제가 있었지만 해결책이 있지만 여기에 게시 할 것입니다. 다른 사람들에게도 유용합니다. 기본적으로 중첩 된 사전을 병합하고 값을 추가하기 위해 저에게 확률을 계산해야했습니다. 하나는 훌륭하게 작동했습니다.

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

위의 방법을 사용하여 병합 할 수 있습니다.

대상 = { '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 }}

또한 여기에서 변경 사항을 확인하십시오.

대상 = { '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}}

병합 = { '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

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

이 코드는 질문에 대답 할 수 있지만,이 코드가 질문에 응답하는 이유 및 / 또는 방법에 대한 추가 컨텍스트를 제공하면 장기적인 가치가 향상됩니다.
xiawi

나는 이것이 마진 될 객체의 유형을 고려하여 하나 이상의 중첩 된 사전을 병합하는 일반적인 구현이라고 생각합니다
Dorcioman

0

한 번 봐 걸릴 toolz패키지를

import toolz
dict1={1:{"a":"A"},2:{"b":"B"}}
dict2={2:{"c":"C"},3:{"d":"D"}}
toolz.merge_with(toolz.merge,dict1,dict2)

준다

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

0

다음 함수는 b를 a로 병합합니다.

def mergedicts(a, b):
    for key in b:
        if isinstance(a.get(key), dict) or isinstance(b.get(key), dict):
            mergedicts(a[key], b[key])
        else:
            a[key] = b[key]
    return a

0

그리고 또 다른 약간의 변형 :

다음은 순수한 python3 세트 기반 딥 업데이트 기능입니다. 한 번에 한 수준을 반복하여 중첩 된 사전을 업데이트하고 다음 각 사전 값 레벨을 업데이트하도록 자체 호출합니다.

def deep_update(dict_original, dict_update):
    if isinstance(dict_original, dict) and isinstance(dict_update, dict):
        output=dict(dict_original)
        keys_original=set(dict_original.keys())
        keys_update=set(dict_update.keys())
        similar_keys=keys_original.intersection(keys_update)
        similar_dict={key:deep_update(dict_original[key], dict_update[key]) for key in similar_keys}
        new_keys=keys_update.difference(keys_original)
        new_dict={key:dict_update[key] for key in new_keys}
        output.update(similar_dict)
        output.update(new_dict)
        return output
    else:
        return dict_update

간단한 예 :

x={'a':{'b':{'c':1, 'd':1}}}
y={'a':{'b':{'d':2, 'e':2}}, 'f':2}

print(deep_update(x, y))
>>> {'a': {'b': {'c': 1, 'd': 2, 'e': 2}}, 'f': 2}

0

다른 답변은 어떻습니까?!? 이것은 또한 돌연변이 / 부작용을 피합니다 :

def merge(dict1, dict2):
    output = {}

    # adds keys from `dict1` if they do not exist in `dict2` and vice-versa
    intersection = {**dict2, **dict1}

    for k_intersect, v_intersect in intersection.items():
        if k_intersect not in dict1:
            v_dict2 = dict2[k_intersect]
            output[k_intersect] = v_dict2

        elif k_intersect not in dict2:
            output[k_intersect] = v_intersect

        elif isinstance(v_intersect, dict):
            v_dict2 = dict2[k_intersect]
            output[k_intersect] = merge(v_intersect, v_dict2)

        else:
            output[k_intersect] = v_intersect

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

assert dict3 == merge(dict1, dict2)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.