更新不同深度的嵌套字典的值
我正在寻找一种方法来更新字典词典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()
而不是{}
。 如果这里的dict
是collections.defaultdict
types的,那么这个特别有用,它可以有不同种类的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}