IT

다양한 깊이의 중첩 된 사전 값 업데이트

lottoking 2020. 6. 13. 09:36
반응형

다양한 깊이의 중첩 된 사전 값 업데이트


덮어 쓰기 레벨 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항목 vA는 dict하고 k원래 업데이트되는 사전에서 키가 아닌 - 그것은 비어있는 새에 그것을 수행하기 때문에 (FM의 코드 "건너 뜀"@ 업데이트의이 부분을 dict어떤 재귀 호출이 반환되면 손실되거나 저장되지 않습니다).

내 다른 변경 사항은 사소합니다. 동일한 작업을 더 빠르고 깨끗하게 수행 할 때 if/ else구문에 대한 이유가 없으며 일반성을 위해 추상 기본 클래스 (구체 클래스가 아닌)에 가장 적합합니다..getisinstance


이것에 대해 조금 알아 보았지만 @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_factorys를 가질 수있는 유형 경우에 특히 유용합니다 .

또한 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

반응형