여러 사전을 병합해야합니다. 예를 들어 다음과 같습니다.
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}}}
파이썬으로 어떻게 쉽게 할 수 있는지 잘 모르겠습니다.
답변
이것은 실제로 까다로울 수 있습니다-특히 일이 일치하지 않을 때 유용한 오류 메시지를 원할 때 복제하지만 일관된 항목을 올바르게 수락합니다 (여기서 다른 대답은 없습니다 …)
많은 수의 항목이 없다고 가정하면 재귀 함수가 가장 쉽습니다.
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
답변
다음은 생성기를 사용하여 쉽게 수행 할 수있는 방법입니다.
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 파일을 병합 하는 것입니다. 따라서 튜플 및 기타 객체를 무시할 수 있습니다. 나에게 합리적인 합병 논리 수단
- 스칼라 교체
- 목록 추가
- 누락 된 키를 추가하고 기존 키를 업데이트하여 병합 받아쓰기
다른 모든 항목과 예상치 못한 결과는 오류를 발생시킵니다.
답변
사전의 사전 병합
이것은 (일반적이지 않더라도) 정식 질문 이므로이 문제를 해결하기 위해 정식 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'
.로 변경 한 후 )
답변
@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
답변
이 간단한 재귀 절차는 충돌하는 키를 재정의하면서 한 사전을 다른 사전으로 병합합니다.
#!/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'}}
답변
@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]