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

时间:2010-07-12 23:01:13

标签: python

我正在寻找一种更新dict dictionary1的方法,用dict update更新内容覆盖levelA

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

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

我可以解决这个问题,因为dictionary1和update可以有任何长度吗?

26 个答案:

答案 0 :(得分:221)

@ FM的答案有正确的总体思路,即递归解决方案,但有些特殊的编码和至少一个bug。我推荐,而不是:

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

当“更新”包含kvvdictk原本不是关键字时,会显示错误在更新的字典中 - @ FM的代码“跳过”这部分更新(因为它在空的新dict上执行它,它不会保存或返回任何地方,只是在递归调用返回时丢失)。

我的其他更改很小:当if更快更干净地执行相同的工作时,else / .get构造没有理由,isinstance最佳应用为了一般性而抽象基类(不是具体的)。

答案 1 :(得分:19)

对我说了一点,但多亏了@ 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 :(得分:15)

@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 :(得分:9)

与接受的解决方案相同,但更清晰的变量命名,文档字符串,并修复了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.utils

此功能位于function showVictoryMessage(playerName) local message = Instance.new("Message") message.Text = playerName .." has won!" message.Parent = game.Workspace wait (2) message.Destroy() end 中的charlatan包中。

答案 4 :(得分:6)

@Alex's answer的微小改进,可以更新不同深度的词典,并限制更新深入到原始嵌套词典的深度(但更新词典深度不受限制)。只有少数案例经过测试:

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

答案 5 :(得分:5)

这是一个不可变版本的递归字典合并,以防任何人需要它。

基于@Alex Martelli的answer

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

答案 6 :(得分:3)

这个问题很旧,但是我在寻找“深度合并”解决方案时就落在了这里。上面的答案启发了接下来的事情。我最终写了我自己的,因为我测试的所有版本中都有错误。错过的关键点是,在两个输入字典的任意深度处,对于某个键k,当d [k]或u [k]不是 字典错误时,决策树。 / p>

此外,此解决方案不需要递归,这与SELECT ((date_trunc('day',nowAtShopLocation)+"OpenAt"::time, date_trunc('day',nowAtShopLocation)+"CloseAt"::time) OVERLAPS(nowAtShopLocation,nowAtShopLocation)) and EXTRACT (ISODOW FROM nowAtShopLocation) <6 from ( select *,now() AT TIME ZONE 'UTC'+(EXTRACT(TIMEZONE_HOUR FROM "OpenAt")||' hour')::interval nowAtShopLocation from your_table ) a 的工作方式更加对称,并返回dict.update()

None

答案 7 :(得分:2)

更新@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,则此功能特别有用。

另请注意u.iteritems()中的u.items()已更改为Python3

答案 8 :(得分:2)

下面的代码应该以正确的方式解决@Alex Martelli的答案中的update({'k1': 1}, {'k1': {'k2': 2}})问题。

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

答案 9 :(得分:2)

我使用@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

答案 10 :(得分:2)

在这两个答案中,作者似乎都理解更新存储在字典中的对象的概念,甚至不是迭代字典项(而不是键)。所以我不得不写一个没有做无意义的重言式词典存储和检索的。 假设dicts存储其他dicts或简单类型。

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

答案 11 :(得分:1)

可能是你偶然发现了一个非标准词典,就像我今天一样,没有iteritems-Attribute。 在这种情况下,很容易将这种类型的字典解释为标准字典。 E.g:

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

答案 12 :(得分:1)

我知道这个问题已经很老了,但是当我不得不更新嵌套字典时,仍然发布了我的工作。我们可以使用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(dict_to_update, path, value):
obj = dict_to_update
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")

这将正确更新字典。

答案 13 :(得分:1)

只需使用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

文档:https://github.com/fabiocaccamo/python-benedict

答案 14 :(得分:0)

您可以尝试一下,它可以与列表一起使用,并且很纯净:

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

答案 15 :(得分:0)

我建议将{}替换为type(v)(),以便传播存储在u中但没有d的任何dict子类的对象类型。例如,这将保留诸如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

答案 16 :(得分:0)

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

使用dictcollections.Mapping

答案 17 :(得分:0)

感谢hobsAlex's answer的评论。确实self.balance会导致update({'k1': 1}, {'k1': {'k2': 2}})

我们应该在函数开始时检查输入值的类型。因此,我建议使用以下功能,该功能可以解决这个(以及其他)问题。

Python 3:

TypeError: 'int' object does not support item assignment.

答案 18 :(得分:0)

我做了一个简单的函数,其中您给键,新值和字典作为输入,它递归地用值更新它:

def update(key,value,dictionary):
    if key in dictionary.keys():
        dictionary[key] = value
        return
    dic_aux = []
    for val_aux in dictionary.values():
        if isinstance(val_aux,dict):
            dic_aux.append(val_aux)
    for i in dic_aux:
        update(key,value,i)
    for [key2,val_aux2] in dictionary.items():
        if isinstance(val_aux2,dict):
            dictionary[key2] = val_aux2

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

#output: {'level1': {'level2': {'levelA': 0, 'levelB': 10}}}

希望它能回答。

答案 19 :(得分:0)

如果您碰巧正在使用 pydantic(很棒的库,顺便说一句),您可以使用它的一种实用方法:

from pydantic.utils import deep_update


dictionary1 = deep_update(dictionary1, update)

答案 20 :(得分:0)

新问 如何通过钥匙链

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

答案 21 :(得分:0)

使用递归的另一种方法:

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)

答案 22 :(得分:0)

是的!还有另一种解决方案。我的解决方案在要检查的键上有所不同。 在所有其他解决方案中,我们仅查看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

答案 23 :(得分:0)

如果要用数组替换“完全嵌套的字典”,则可以使用以下代码段:

它将用“ 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

答案 24 :(得分:-1)

如果您想要单线:

{**dictionary1, **{'level1':{**dictionary1['level1'], **{'level2':{**dictionary1['level1']['level2'], **{'levelB':10}}}}}}

答案 25 :(得分:-1)

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

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