덮어 쓰기 레벨 A를 사용하여 dict update1의 내용으로 dict dictionary1을 업데이트하는 방법을 찾고 있습니다.
dictionary1={'level1':{'level2':{'levelA':0,'levelB':1}}}
update={'level1':{'level2':{'levelB':10}}}
dictionary1.update(update)
print dictionary1
{'level1': {'level2': {'levelB': 10}}}
업데이트는 가장 낮은 키 level1을 업데이트하기 때문에 level2의 값을 삭제한다는 것을 알고 있습니다.
dictionary1과 update가 길이를 가질 수 있다면 어떻게 해결할 수 있습니까?
답변
@FM의 대답은 올바른 일반적인 아이디어, 즉 재귀 솔루션이지만 다소 독특한 코딩과 적어도 하나의 버그를 가지고 있습니다. 대신 권장합니다.
파이썬 2 :
import collections
def update(d, u):
for k, v in u.iteritems():
if isinstance(v, collections.Mapping):
d[k] = update(d.get(k, {}), v)
else:
d[k] = v
return d
파이썬 3 :
import collections.abc
def update(d, u):
for k, v in u.items():
if isinstance(v, collections.abc.Mapping):
d[k] = update(d.get(k, {}), v)
else:
d[k] = v
return d
“업데이트”가있을 때까지 버그 쇼 k
, v
항목 v
A는 dict
하고 k
원래 업데이트되는 사전에서 키가 아닌 – 그것은 비어있는 새에 그것을 수행하기 때문에 (FM의 코드 “건너 뜀”@ 업데이트의이 부분을 dict
어떤 재귀 호출이 반환되면 손실되거나 저장되지 않습니다).
내 다른 변경 사항은 사소합니다. 동일한 작업을 더 빠르고 깔끔하게 수행 할 때 if
/ else
구문에 대한 이유가 없으며 일반성을 위해 추상 기본 클래스 (구체 클래스가 아닌)에 가장 적합합니다..get
isinstance
답변
이것에 대해 조금 알아 차 렸지만 @Alex의 게시물 덕분에 그는 누락 된 격차를 메 웠습니다. 그러나 재귀 내의 값이 인 경우 문제가 dict
발생 list
했기 때문에 공유하고 답변을 확장 할 것이라고 생각했습니다.
import collections
def update(orig_dict, new_dict):
for key, val in new_dict.iteritems():
if isinstance(val, collections.Mapping):
tmp = update(orig_dict.get(key, { }), val)
orig_dict[key] = tmp
elif isinstance(val, list):
orig_dict[key] = (orig_dict.get(key, []) + val)
else:
orig_dict[key] = new_dict[key]
return orig_dict
답변
@Alex의 대답은 좋지만 정수와 같은 요소를 사전과 같은 사전으로 바꾸면 작동하지 않습니다 update({'foo':0},{'foo':{'bar':1}})
. 이 업데이트는 다음을 해결합니다.
import collections
def update(d, u):
for k, v in u.iteritems():
if isinstance(d, collections.Mapping):
if isinstance(v, collections.Mapping):
r = update(d.get(k, {}), v)
d[k] = r
else:
d[k] = u[k]
else:
d = {k: u[k]}
return d
update({'k1': 1}, {'k1': {'k2': {'k3': 3}}})
답변
허용되는 것과 동일한 솔루션이지만 더 명확한 변수 이름 지정, docstring 및 {}
값으로 무시되지 않는 버그가 수정 되었습니다.
import collections
def deep_update(source, overrides):
"""
Update a nested dictionary or similar mapping.
Modify ``source`` in place.
"""
for key, value in overrides.iteritems():
if isinstance(value, collections.Mapping) and value:
returned = deep_update(source.get(key, {}), value)
source[key] = returned
else:
source[key] = overrides[key]
return source
다음은 몇 가지 테스트 사례입니다.
def test_deep_update():
source = {'hello1': 1}
overrides = {'hello2': 2}
deep_update(source, overrides)
assert source == {'hello1': 1, 'hello2': 2}
source = {'hello': 'to_override'}
overrides = {'hello': 'over'}
deep_update(source, overrides)
assert source == {'hello': 'over'}
source = {'hello': {'value': 'to_override', 'no_change': 1}}
overrides = {'hello': {'value': 'over'}}
deep_update(source, overrides)
assert source == {'hello': {'value': 'over', 'no_change': 1}}
source = {'hello': {'value': 'to_override', 'no_change': 1}}
overrides = {'hello': {'value': {}}}
deep_update(source, overrides)
assert source == {'hello': {'value': {}, 'no_change': 1}}
source = {'hello': {'value': {}, 'no_change': 1}}
overrides = {'hello': {'value': 2}}
deep_update(source, overrides)
assert source == {'hello': {'value': 2, 'no_change': 1}}
이 기능은의 charlatan 패키지 에서 사용할 수 있습니다 charlatan.utils
.
답변
누군가가 필요로 할 경우를 대비하여 재귀 사전 병합의 불변 버전이 있습니다.
@Alex Martelli의 답변을 바탕으로 .
파이썬 2.x :
import collections
from copy import deepcopy
def merge(dict1, dict2):
''' Return a new dictionary by merging two dictionaries recursively. '''
result = deepcopy(dict1)
for key, value in dict2.iteritems():
if isinstance(value, collections.Mapping):
result[key] = merge(result.get(key, {}), value)
else:
result[key] = deepcopy(dict2[key])
return result
파이썬 3.x :
import collections
from copy import deepcopy
def merge(dict1, dict2):
''' Return a new dictionary by merging two dictionaries recursively. '''
result = deepcopy(dict1)
for key, value in dict2.items():
if isinstance(value, collections.Mapping):
result[key] = merge(result.get(key, {}), value)
else:
result[key] = deepcopy(dict2[key])
return result
답변
깊이가 다른 사전을 업데이트하고 업데이트가 원래 중첩 사전으로 다이빙하는 깊이를 제한 할 수있는 @Alex의 답변 이 약간 개선되었습니다 (업데이트 사전 깊이는 제한되지 않음). 몇 가지 사례 만 테스트되었습니다.
def update(d, u, depth=-1):
"""
Recursively merge or update dict-like objects.
>>> update({'k1': {'k2': 2}}, {'k1': {'k2': {'k3': 3}}, 'k4': 4})
{'k1': {'k2': {'k3': 3}}, 'k4': 4}
"""
for k, v in u.iteritems():
if isinstance(v, Mapping) and not depth == 0:
r = update(d.get(k, {}), v, depth=max(depth - 1, -1))
d[k] = r
elif isinstance(d, Mapping):
d[k] = u[k]
else:
d = {k: u[k]}
return d
답변
이 질문은 오래되었지만 “깊은 병합”솔루션을 검색 할 때 여기에 왔습니다. 위의 답변은 다음과 같은 내용에 영감을주었습니다. 테스트 한 모든 버전에 버그가 있었기 때문에 직접 작성했습니다. 누락 된 임계점은 d [k] 또는 u [k]가 아닌 경우 일부 입력 키의 경우 k에 대한 결정 트리의 임의의 깊이에서 받아쓰기 였습니다.
또한이 솔루션에는 재귀가 필요하지 않습니다. 재귀는 dict.update()
작동 방식 과 더 대칭이며을 반환합니다 None
.
import collections
def deep_merge(d, u):
"""Do a deep merge of one dict into another.
This will update d with values in u, but will not delete keys in d
not found in u at some arbitrary depth of d. That is, u is deeply
merged into d.
Args -
d, u: dicts
Note: this is destructive to d, but not u.
Returns: None
"""
stack = [(d,u)]
while stack:
d,u = stack.pop(0)
for k,v in u.items():
if not isinstance(v, collections.Mapping):
# u[k] is not a dict, nothing to merge, so just set it,
# regardless if d[k] *was* a dict
d[k] = v
else:
# note: u[k] is a dict
# get d[k], defaulting to a dict, if it doesn't previously
# exist
dv = d.setdefault(k, {})
if not isinstance(dv, collections.Mapping):
# d[k] is not a dict, so just set it to u[k],
# overriding whatever it was
d[k] = v
else:
# both d[k] and u[k] are dicts, push them on the stack
# to merge
stack.append((dv, v))