다양한 깊이의 중첩 된 사전 값 업데이트
덮어 쓰기 레벨 A로 dict update의 내용으로 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의 대답은 올바른 일반적인 아이디어, 즉 재귀 솔루션이지만 다소 독특한 코딩과 적어도 하나의 버그를 가지고 있습니다. 대신 권장합니다.
import collections
import six
# python 3.8+ compatibility
try:
collectionsAbc = collections.abc
except:
collectionsAbc = collections
def update(d, u):
for k, v in six.iteritems(u):
dv = d.get(k, {})
if not isinstance(dv, collectionsAbc.Mapping):
d[k] = v
elif isinstance(v, collectionsAbc.Mapping):
d[k] = update(dv, 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의 답변 이 약간 개선되었습니다 (업데이트 사전 깊이는 제한되지 않음). 몇 가지 사례 만 테스트되었습니다.
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
누군가가 필요로 할 경우를 대비하여 재귀 사전 병합의 불변 버전이 있습니다.
@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
이 질문은 오래되었지만 "깊은 병합"솔루션을 검색 할 때 여기에 왔습니다. 위의 답변은 다음과 같은 내용에 영감을주었습니다. 테스트 한 모든 버전에 버그가 있었기 때문에 직접 작성했습니다. 누락 된 임계점은 두 개의 입력 받아쓰기의 임의의 깊이에서 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))
이 답변들 중 어느 것도 저자는 사전에 저장된 객체를 업데이트하거나 사전 키를 반복하는 개념 (키와 반대)의 개념을 이해하지 못하는 것 같습니다. 그래서 나는 무의미한 타우 톨릭 사전 상점과 검색을하지 않는 것을 작성해야했습니다. dicts는 다른 dicts 또는 simple 유형을 저장한다고 가정합니다.
def update_nested_dict(d, other):
for k, v in other.items():
if isinstance(v, collections.Mapping):
d_v = d.get(k)
if isinstance(d_v, collections.Mapping):
update_nested_dict(d_v, v)
else:
d[k] = v.copy()
else:
d[k] = v
또는 모든 유형으로 작업하는 것이 더 간단합니다.
def update_nested_dict(d, other):
for k, v in other.items():
d_v = d.get(k)
if isinstance(v, collections.Mapping) and isinstance(d_v, collections.Mapping):
update_nested_dict(d_v, v)
else:
d[k] = deepcopy(v) # or d[k] = v if you know what you're doing
솔루션을보다 강력하게 만들기 위해 코드의 버그를 수정하기 위해 @Alex Martelli의 답변으로 업데이트하십시오.
def update_dict(d, u):
for k, v in u.items():
if isinstance(v, collections.Mapping):
default = v.copy()
default.clear()
r = update_dict(d.get(k, default), v)
d[k] = r
else:
d[k] = v
return d
핵심은 재귀에서 종종 같은 유형 을 만들고 싶기 때문에 여기서는 사용 v.copy().clear()
하지만 사용 하지는 않습니다 {}
. 그리고 이것은 dict
여기에 collections.defaultdict
다른 종류의 default_factory
s를 가질 수있는 유형 인 경우에 특히 유용합니다 .
또한 u.iteritems()
로 변경 u.items()
되었습니다 Python3
.
@Alex Martelli가 제안한 솔루션을 사용했지만 실패합니다.
TypeError 'bool' object does not support item assignment
두 사전이 어떤 수준에서 데이터 유형이 다른 경우.
같은 수준에서 사전의 요소 d
는 스칼라 (예 :) 인 Bool
반면 사전의 요소 u
는 여전히 딕셔너리 인 경우 사전 할당이 스칼라에 할당 될 수 없으므로 (예 :) 재 할당이 실패합니다 True[k]
.
추가 된 조건 하나는 다음과 같습니다.
from collections import Mapping
def update_deep(d, u):
for k, v in u.items():
# this condition handles the problem
if not isinstance(d, Mapping):
d = u
elif isinstance(v, Mapping):
r = update_deep(d.get(k, {}), v)
d[k] = r
else:
d[k] = u[k]
return d
iteritems-Attribute가없는 오늘과 같이 비표준 사전을 우연히 발견했을 수 있습니다. 이 경우 이러한 유형의 사전을 표준 사전으로 쉽게 해석 할 수 있습니다. 예 :
import collections
def update(orig_dict, new_dict):
for key, val in dict(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[key] + val)
else:
orig_dict[key] = new_dict[key]
return orig_dict
import multiprocessing
d=multiprocessing.Manager().dict({'sample':'data'})
u={'other': 1234}
x=update(d, u)
x.items()
def update(value, nvalue):
if not isinstance(value, dict) or not isinstance(nvalue, dict):
return nvalue
for k, v in nvalue.items():
value.setdefault(k, dict())
if isinstance(v, dict):
v = update(value[k], v)
value[k] = v
return value
사용 dict
또는collections.Mapping
I know this question is pretty old, but still posting what I do when I have to update a nested dictionary. We can use the fact that dicts are passed by reference in python Assuming that the path of the key is known and is dot separated. Forex if we have a dict named data:
{
"log_config_worker": {
"version": 1,
"root": {
"handlers": [
"queue"
],
"level": "DEBUG"
},
"disable_existing_loggers": true,
"handlers": {
"queue": {
"queue": null,
"class": "myclass1.QueueHandler"
}
}
},
"number_of_archived_logs": 15,
"log_max_size": "300M",
"cron_job_dir": "/etc/cron.hourly/",
"logs_dir": "/var/log/patternex/",
"log_rotate_dir": "/etc/logrotate.d/"
}
And we want to update the queue class, the path of the key would be - log_config_worker.handlers.queue.class
We can use the following function to update the value:
def get_updated_dict(obj, path, value):
key_list = path.split(".")
for k in key_list[:-1]:
obj = obj[k]
obj[key_list[-1]] = value
get_updated_dict(data, "log_config_worker.handlers.queue.class", "myclass2.QueueHandler")
This would update the dictionary correctly.
Just use python-benedict
(I did it), it has a merge
(deepupdate) utility method and many others. It works with python 2 / python 3 and it is well tested.
from benedict import benedict
dictionary1=benedict({'level1':{'level2':{'levelA':0,'levelB':1}}})
update={'level1':{'level2':{'levelB':10}}}
dictionary1.merge(update)
print(dictionary1)
# >> {'level1':{'level2':{'levelA':0,'levelB':10}}}
Installation: pip install python-benedict
Documentation: https://github.com/fabiocaccamo/python-benedict
If you want to replace a "full nested dictionnay with arrays" you can use this snippet :
It will replace any "old_value" by "new_value". It's roughly doing a depth-first rebuilding of the dictionnary. It can even work with List or Str/int given as input parameter of first level.
def update_values_dict(original_dict, future_dict, old_value, new_value):
# Recursively updates values of a nested dict by performing recursive calls
if isinstance(original_dict, Dict):
# It's a dict
tmp_dict = {}
for key, value in original_dict.items():
tmp_dict[key] = update_values_dict(value, future_dict, old_value, new_value)
return tmp_dict
elif isinstance(original_dict, List):
# It's a List
tmp_list = []
for i in original_dict:
tmp_list.append(update_values_dict(i, future_dict, old_value, new_value))
return tmp_list
else:
# It's not a dict, maybe a int, a string, etc.
return original_dict if original_dict != old_value else new_value
Yes! And another solution. My solution differs in the keys that are being checked. In all other solutions we only look at the keys in dict_b
. But here we look in the union of both dictionaries.
Do with it as you please
def update_nested(dict_a, dict_b):
set_keys = set(dict_a.keys()).union(set(dict_b.keys()))
for k in set_keys:
v = dict_a.get(k)
if isinstance(v, dict):
new_dict = dict_b.get(k, None)
if new_dict:
update_nested(v, new_dict)
else:
new_value = dict_b.get(k, None)
if new_value:
dict_a[k] = new_value
If you want a one-liner:
{**dictionary1, **{'level1':{**dictionary1['level1'], **{'level2':{**dictionary1['level1']['level2'], **{'levelB':10}}}}}}
That's a bit to the side but do you really need nested dictionaries? Depending on the problem, sometimes flat dictionary may suffice... and look good at it:
>>> dict1 = {('level1','level2','levelA'): 0}
>>> dict1['level1','level2','levelB'] = 1
>>> update = {('level1','level2','levelB'): 10}
>>> dict1.update(update)
>>> print dict1
{('level1', 'level2', 'levelB'): 10, ('level1', 'level2', 'levelA'): 0}
참고URL : https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth
'IT' 카테고리의 다른 글
술어와 일치하는 순서로 첫 번째 요소 찾기 (0) | 2020.06.13 |
---|---|
모범 사례가없는 경우 SQL Server 삽입 (0) | 2020.06.13 |
루프 나 조건문없이 1부터 1000까지 인쇄하는 C 코드는 어떻게 작동합니까? (0) | 2020.06.13 |
동일한 네트워크의 다른 컴퓨터에서이 로컬 호스트에 어떻게 연결합니까? (0) | 2020.06.13 |
Windows : 명령 프롬프트에서 여러 줄 명령을 지정하는 방법? (0) | 2020.06.13 |