问题:更新嵌套深度不同的字典的值
我正在寻找一种方法来用字典更新内容覆盖字典字典A
dictionary1={'level1':{'level2':{'levelA':0,'levelB':1}}}
update={'level1':{'level2':{'levelB':10}}}
dictionary1.update(update)
print dictionary1
{'level1': {'level2': {'levelB': 10}}}
我知道update会删除level2中的值,因为它正在更新最低的密钥level1。
鉴于dictionary1和update可以有任何长度,我该如何解决?
回答 0
@FM的答案具有正确的总体思路,即递归解决方案,但有些特殊的编码和至少一个错误。我建议改为:
Python 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
Python 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
是dict
和k
最初不是被更新在字典中的关键- @ FM代码“跳过”更新的这一部分(因为它执行它的新的空dict
其不会在任何地方保存或返回,而是在递归调用返回时丢失)。
我的其他改动很小:没有理由让if
/ else
构造何时.get
更快,更干净地完成相同的工作,并且isinstance
为了通用起见,最好将其应用于抽象基类(而不是具体的基类)。
回答 1
我花了一点时间,但是由于@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
回答 2
@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}}})
回答 3
与已接受的解决方案相同,但更清晰的变量命名,文档字符串,并修复了一个错误,{}
即不能覆盖值的错误。
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
。
回答 4
如果有人需要,这是递归字典合并的不可变版本。
基于@Alex Martelli的答案。
Python 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
Python 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
回答 5
对@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
回答 6
这个问题很旧,但是我在寻找“深度合并”解决方案时就落在了这里。上面的答案启发了接下来的事情。我最终写了我自己的,因为我测试的所有版本中都有错误。错过的临界点是,在两个输入字典的任意深度处,对于某个键k,当d [k]或u [k] 不是字典时的决策树是错误的。
而且,此解决方案不需要递归,这与dict.update()
工作方式和return 更加对称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))
回答 7
只需使用python-benedict
(我做到了),它就有一个merge
(deepupdate)实用程序方法和许多其他方法。它与python 2 / python 3一起工作,并且经过了良好的测试。
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}}}
安装: pip install python-benedict
回答 8
在这两个答案中,作者似乎都没有理解更新字典中存储的对象,甚至迭代字典项(与键相对)的概念。因此,我不得不写一篇不会造成毫无意义的重言式字典存储和检索的书。假定这些字典存储其他字典或简单类型。
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
回答 9
更新@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()
not {}
。如果这是可以具有不同类型s dict
的类型collections.defaultdict
,则这特别有用default_factory
。
另请注意,u.iteritems()
已将更改为u.items()
中的Python3
。
回答 10
我使用了@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
回答 11
下面的代码应update({'k1': 1}, {'k1': {'k2': 2}})
以正确的方式解决@Alex Martelli的答案中的问题。
def deepupdate(original, update):
"""Recursively update a dict.
Subdict's won't be overwritten but also updated.
"""
if not isinstance(original, abc.Mapping):
return update
for key, value in update.items():
if isinstance(value, abc.Mapping):
original[key] = deepupdate(original.get(key, {}), value)
else:
original[key] = value
return original
回答 12
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
回答 13
我知道这个问题已经很老了,但是当我不得不更新嵌套字典时,仍然会发布我的操作。我们可以使用dict通过python中的引用传递的事实,假设键的路径是已知的并且是点分隔的。外汇,如果我们有一个名为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/"
}
而且我们要更新队列类,键的路径将是- log_config_worker.handlers.queue.class
我们可以使用以下函数来更新值:
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")
这样可以正确更新字典。
回答 14
可能是您偶然发现了像今天这样的非标准词典,该词典没有iteritems-Attribute。在这种情况下,很容易将这种类型的词典解释为标准词典。例如: Python 2.7:
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()
Python 3.8:
def update(orig_dict, new_dict):
orig_dict=dict(orig_dict)
for key, val in dict(new_dict).items():
if isinstance(val, collections.abc.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 collections
import multiprocessing
d=multiprocessing.Manager().dict({'sample':'data'})
u={'other': 1234, "deeper": {'very': 'deep'}}
x=update(d, u)
x.items()
回答 15
是! 和另一个解决方案。我的解决方案在要检查的键上有所不同。在所有其他解决方案中,我们仅查看中的键dict_b
。但是这里我们看一下两个词典的结合。
随便做吧
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
回答 16
如果要用数组替换“完全嵌套的字典”,可以使用以下代码段:
它将用“ new_value”替换任何“ old_value”。它大致在对字典进行深度优先的重建。它甚至可以与作为第一级输入参数的List或Str / int一起使用。
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
回答 17
使用递归的另一种方法:
def updateDict(dict1,dict2):
keys1 = list(dict1.keys())
keys2= list(dict2.keys())
keys2 = [x for x in keys2 if x in keys1]
for x in keys2:
if (x in keys1) & (type(dict1[x]) is dict) & (type(dict2[x]) is dict):
updateDict(dict1[x],dict2[x])
else:
dict1.update({x:dict2[x]})
return(dict1)
回答 18
一个新的Q如何通过钥匙链
dictionary1={'level1':{'level2':{'levelA':0,'levelB':1}},'anotherLevel1':{'anotherLevel2':{'anotherLevelA':0,'anotherLevelB':1}}}
update={'anotherLevel1':{'anotherLevel2':1014}}
dictionary1.update(update)
print dictionary1
{'level1':{'level2':{'levelA':0,'levelB':1}},'anotherLevel1':{'anotherLevel2':1014}}
回答 19
您可以尝试一下,它可以与列表一起使用,而且很纯粹:
def update_keys(newd, dic, mapping):
def upsingle(d,k,v):
if k in mapping:
d[mapping[k]] = v
else:
d[k] = v
for ekey, evalue in dic.items():
upsingle(newd, ekey, evalue)
if type(evalue) is dict:
update_keys(newd, evalue, mapping)
if type(evalue) is list:
upsingle(newd, ekey, [update_keys({}, i, mapping) for i in evalue])
return newd
回答 20
我建议,以取代{}
通过type(v)()
以存储在任何字典子类的繁殖对象类型,u
但不存在d
。例如,这将保留诸如collections.OrderedDict之类的类型:
Python 2:
import collections
def update(d, u):
for k, v in u.iteritems():
if isinstance(v, collections.Mapping):
d[k] = update(d.get(k, type(v)()), v)
else:
d[k] = v
return d
Python 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, type(v)()), v)
else:
d[k] = v
return d
回答 21
这有点偏,但是您真的需要嵌套字典吗?根据问题的不同,有时平字典可能就足够了……并且看起来很不错:
>>> 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}
回答 22
如果您想要单线:
{**dictionary1, **{'level1':{**dictionary1['level1'], **{'level2':{**dictionary1['level1']['level2'], **{'levelB':10}}}}}}