更新不同深度的嵌套字典的值

我正在寻找一种方法来更新字典词典1与字典更新的内容wihout覆盖levelA

dictionary1={'level1':{'level2':{'levelA':0,'levelB':1}}} update={'level1':{'level2':{'levelB':10}}} dictionary1.update(update) print dictionary1 {'level1': {'level2': {'levelB': 10}}} 

我知道更新会删除level2中的值,因为它正在更新最低的关键级别1。

鉴于字典1和更新可以有任何长度,我怎么能解决这个问题?

@ FM的答案有一个正确的概念,即一个recursion的解决scheme,但有点奇怪的编码和至less一个错误。 我build议,而不是:

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 def update(d, u): for k, v in u.items(): if isinstance(v, collections.Mapping): d[k] = update(d.get(k, {}), v) else: d[k] = v return d 

当“更新”有一个k,v项目,其中v是一个字典,k不是字典中更新的关键时,bug就会出现 – @ FM的代码“跳过”这部分更新(因为它执行一个空的新的字典,不保存或返回任何地方,只是当recursion调用返回时丢失)。

我的其他变化是微小的:当.get做同样的工作更快,更干净时,if / else结构没有任何理由,而isinstance最适用于抽象基类(不是具体的)。

在这个问题上我带了一点点,但是感谢@ Alex的职位,他填补了我失踪的空白。 但是,如果recursiondict某个值恰好是一个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的答案是好的,但用字典replace元素(如整数)时不起作用,如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}}}) 

对@ Alex的答案进行了细微的改进,使更新不同深度的字典成为可能,同时限制更新深入原始嵌套字典的深度(但更新的字典深度不受限制)。 只有less数情况经过testing:

 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 

与接受的解决scheme相同,但更清晰的variables命名,文档string,并修复了{}作为值不会覆盖的错误。

 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 

这里有几个testing用例:

 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的答案是修复他的代码中的一个错误,使解决scheme更强大:

 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 

关键是我们经常要在recursion中创build相同的types ,所以我们在这里使用v.copy().clear()而不是{} 。 如果这里的dictcollections.defaultdicttypes的,那么这个特别有用,它可以有不同种类的default_factory

还要注意在u.iteritems()中, u.iteritems()已经被改为u.items()

这是一个不可变的recursion字典合并版本,以防有人需要。

基于@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 

在这些答案中,作者似乎都不了解更新存储在字典中的对象的概念,甚至无法遍历字典项目(而不是键)。 所以我不得不写一个没有无意义的重复词典存储和检索。 假定字典存储其他字典或简单types。

 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 

或者更简单的与任何types的工作:

 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 Martellibuild议的解决scheme,但是失败了

TypeError 'bool' object does not support item assignment

当两个字典在某种程度上有不同的数据types时。

在同一级别的情况下,字典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一样。 在这种情况下,很容易将这种types的字典解释为标准字典。 例如:

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

这有点偏端,但你真的需要嵌套字典吗? 根据这个问题,有时候扁平的字典可能就足够了…并且看起来很好:

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