developer tip

사전 병합 사전

optionbox 2020. 8. 9. 10:22
반응형

사전 병합 사전


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

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

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


이것은 실제로 매우 까다 롭습니다. 특히 중복되지만 일관된 항목을 올바르게 수락하면서 일관되지 않은 유용한 오류 메시지를 원할 경우 (다른 답변은 없습니다 ....)

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

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는 (아래) 두 개 이상의 딕셔너리가있을 수 있다고 지적했습니다.이 경우 다음을 사용할 수 있습니다.

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

모든 것이 dict1에 추가됩니다.

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

ps in python 3, 당신은 또한 필요합니다 from functools import reduce


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

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

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

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

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


사전 병합 사전

이것이 (특정 비 일반성에도 불구하고) 표준 질문이므로이 문제를 해결하기위한 표준 Python 접근 방식을 제공하고 있습니다.

가장 간단한 경우 : "잎은 빈 딕셔너리로 ​​끝나는 중첩 딕셔너리입니다":

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의 값이 None이고 첫 번째 값이 a 인 경우에도 두 번째 dict의 값이 첫 번째를 덮어 씁니다. 많은 정보가 담긴 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'.)


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

def merge (a, b, path = None, update = True) :
    "http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge"
    "b를 a에 병합"
    경로가 없음 인 경우 : 경로 = []
    b의 키 :
        키 입력 :
            isinstance (a [key], dict) 및 isinstance (b [key], dict) :
                merge (a [키], b [키], 경로 + [str (키)])
            elif a [key] == b [key] :
                # 동일한 리프 값 전달
            elif isinstance (a [key], list) 및 isinstance (b [key], list) :
                idx의 경우 enumerate (b [key])의 val :
                    a [key] [idx] = merge (a [key] [idx], b [key] [idx], 경로 + [str (key), str (idx)], update = update)
            elif 업데이트 :
                a [키] = b [키]
            그밖에:
                Exception ( 'Conflict at % s'% '.'. join (path + [str (key)])) 발생
        그밖에:
            a [키] = b [키]
    반환

알 수없는 사전 수준이 있으면 재귀 함수를 제안합니다.

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의 답변을 기반으로합니다. 더 나은 방법으로 중첩 된 목록을 처리합니다.

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]

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

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

개요

다음 접근 방식은 dict의 깊은 병합 문제를 다음과 같이 세분화합니다.

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

  2. f함께 사용할 재귀 병합 기능merge


이행

두 개의 (중첩되지 않은) 딕셔너리를 병합하는 함수는 많은 방법으로 작성할 수 있습니다. 나는 개인적으로 좋아한다

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

적절한 recurrsive 합병 함수를 정의의 좋은 방법은 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)

두 개의 중첩 된 사전을 병합하려면 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'}} 

노트:

이 접근 방식의 장점은 다음과 같습니다.

  • 이 함수는 코드를 추론하고 테스트하기 더 간단하게 만드는 단일 작업을 수행하는 더 작은 함수에서 빌드됩니다.

  • 이 동작은 하드 코딩되지 않지만 필요에 따라 변경 및 확장하여 코드 재사용을 향상시킬 수 있습니다 (아래 예제 참조).


커스터마이징

일부 답변은 다른 (잠재적으로 중첩 된) 사전 목록을 포함하는 사전도 고려했습니다. 이 경우 목록 위에지도를 표시하고 위치에 따라 병합 할 수 있습니다. 이것은 병합 함수에 다른 정의를 추가하여 수행 할 수 있습니다 f.

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

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, {})

이 버전의 함수는 N 개의 딕셔너리와 딕셔너리 만 고려합니다. 부적합한 매개 변수를 전달할 수 없습니다. 그렇지 않으면 TypeError가 발생합니다. 병합 자체는 주요 충돌을 설명하며 병합 체인 아래에있는 사전의 데이터를 덮어 쓰는 대신 값 집합을 만들고 여기에 추가합니다. 데이터가 손실되지 않습니다.

페이지에서 가장 효율적이지 않을 수도 있지만 가장 철저하고 2 대 N 딕셔너리를 병합 할 때 정보를 잃지 않을 것입니다.

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}


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가 발생합니다. 여기에는 함수에 대한 초기 호출과 재귀 호출이 모두 포함됩니다. 이것이 바로 내가 원했던 것이기 때문에 나는 그것을 떠났다. 재귀 호출에 의해 발생 된 AttributeErrors를 쉽게 포착 한 다음 원하는 값을 산출 할 수 있습니다.


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


각각 여러 개의 중첩 된 사전을 포함 할 수있는 두 개의 사전 ( 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'

병합 된지도에 계속 표시됩니다.


짧고 달콤한 :

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]

이것은 Python의 dict.update방법 과 같이 작동하며 빌드됩니다 . dict 를 제자리에서 업데이트 할 None반환됩니다 ( return d원하는 경우 언제든지 추가 할 수 있음) d. 키 입력은 v기존의 모든 키를 덮어 씁니다 d(딕셔너리의 내용을 해석하지 않습니다).

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


누군가이 문제에 대한 또 다른 접근 방식을 원할 경우 여기에 내 해결책이 있습니다.

미덕 : 짧고, 선언적이며, 스타일이 기능적입니다 (재귀 적, 변형 없음).

잠재적 인 단점 : 이것은 당신이 찾고있는 병합이 아닐 수도 있습니다. 의미론은 독 스트링을 참조하십시오.

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
        }

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

전체 옵션 목록은 문서를 확인하세요 !


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

  • 딕셔너리는 딕셔너리가 아닌 값 {"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'}}

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

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의 값을 사용하여 충돌을 해결하지만이를 쉽게 재정의 할 수 있습니다. 일부 요술을 사용하면 예외를 던질 수도 있습니다. :).


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

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

#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

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

참고 URL : https://stackoverflow.com/questions/7204805/dictionaries-of-dictionaries-merge

반응형