简体   繁体   中英

How to update a dict nested in another?

I have the below original dict:

{'data_extraction': {'if_extraction': False,
  'path_data': 'data_extraction/extractions.sql',
  'set_params': {'START_DT': "'201901'",
   'END_DT': "'202104'"
                         },
  'calibration': True,
  'target_variable': 'unsure'}}

I'd like to replace the set params bit with the following dict:

dict1= {'set_params': {'START_DT': "'201001'",
   'END_DT': "'201004'"
}}

I took this code from a similar question:

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

update(original,dict1)

Applied it to the above and I got:

 {'data_extraction': {'if_extraction': False,
          'path_data': 'data_extraction/extractions.sql',
          'set_params': {'START_DT': "'201901'",
           'END_DT': "'202104'"
                                 },
          'calibration': True,
          'target_variable': 'unsure'},
           'set_params': {'START_DT': "'201001'",
            'END_DT': "'201004'" }}

So rather than replace, it appended the replacement dict at the end. How can I update values in a dict such that if I pass another dict as I have with dict1 it replaces it? What am I doing wrong?

These days Python dicts have an update method to update from another dict. Be sure to specify the appropriate dicts that you want to update and update from!

original['data_extraction']['set_params'].update(dict1['set_params'])

Or possibly

original['data_extraction'].update(dict1)

if there can be other keys besides 'set_params' in dict1 and you want to update those as well.

Use recursive function with dict.update() . This way you don't have to sepcify the keys in case of multilevel dictionary.

def update_dict(source_dict, dict1):
    for key in source_dict:
        if key in dict1.keys():
            source_dict.update(dict1)
        elif isinstance(source_dict[key],dict):
            update_dict(source_dict[key], dict1)


source_dict = {'data_extraction': {'if_extraction': False,
      'path_data': 'data_extraction/extractions.sql',
      'set_params': {'START_DT': "'201901'",
       'END_DT': "'202104'"},
      'calibration': True,
      'target_variable': 'unsure'}}

dict1= {'set_params': {'START_DT': "'201001'",
   'END_DT': "'201004'"
}}

update_dict(source_dict, dict1)

print(source_dict)

>> 
{'data_extraction': {'if_extraction': False,
  'path_data': 'data_extraction/extractions.sql',
  'set_params': {'START_DT': "'201001'", 'END_DT': "'201004'"},
  'calibration': True,
  'target_variable': 'unsure'}}

You were almost there... In this implementation, I use deepcopy() to avoid overwriting the original dictionary.

from copy import deepcopy

def update(d, u):
    r = deepcopy(d)
    for k, v in r.items():
        if type(v) is dict:
            for _k, _v in v.items():
                if _k in u:
                    r[k][_k] = u[_k]
        elif k in u:
            r[k] = u[k]
    return r


a = {'data_extraction':{'if_extraction': False,
                         'path_data': 'data_extraction/extractions.sql',
                         'set_params': {'START_DT': "'201901'", 'END_DT': "'202104'"},
                         'calibration': True,
                         'target_variable': 'unsure'}
     }

b = {'set_params': {'START_DT': "'201001'", 'END_DT': "'201004'"}}

c = update(a, b)

print(a, '\n')
print(b, '\n')
print(c, '\n')

Output:

{'data_extraction': {'if_extraction': False, 'path_data': 'data_extraction/extractions.sql', 'set_params': {'START_DT': "'201901'", 'END_DT': "'202104'"}, 'calibration': True, 'target_variable': 'unsure'}} 

{'set_params': {'START_DT': "'201001'", 'END_DT': "'201004'"}} 

{'data_extraction': {'if_extraction': False, 'path_data': 'data_extraction/extractions.sql', 'set_params': {'START_DT': "'201001'", 'END_DT': "'201004'"}, 'calibration': True, 'target_variable': 'unsure'}}

In a dict when you asing something to a key that doesnt exists, it is appended and then the content is added. If you want do substitute some key you have to delete it first.
Use pop for that ( yourdict.pop ("key to delete") ), then you can add the other key normally.

You need to have the recursive function check each (nested) dictionary to see if it contains the key that needs to be replaced (the target_key ). In the code below this is retrieved from the replacement dictionary argument new_dict that's passed to the update() function. This only needs to be done once, so there's a nested_update() function that does actual replacing.

import collections.abc

original = {
  "data_extraction": {
    "if_extraction": False,
    "path_data": "data_extraction/extractions.sql",
    "set_params": {
      "START_DT": "'201901'",
      "END_DT": "'202104'"
    },
    "calibration": True,
    "target_key_variable": "unsure"
  }
}

dict1 = {
  "set_params": {
    "START_DT": "'201001'",
    "END_DT": "'201004'"
  }
}


def update(d, new_dict):
    target_key, new_value = list(new_dict.items())[0]

    def nested_update(d, new_dict):
        for key, value in d.items():
            if key == target_key:
                d[key] = new_value
            elif isinstance(value, collections.abc.Mapping):
                d[key] = nested_update(value, new_dict)
        return d

    nested_update(d, new_dict)
    return d

update(original, dict1)

import json
print(json.dumps(original, indent=2))  # Pretty-print result.

Output:

{
  "data_extraction": {
    "if_extraction": false,
    "path_data": "data_extraction/extractions.sql",
    "set_params": {
      "START_DT": "'201001'",
      "END_DT": "'201004'"
    },
    "calibration": true,
    "target_key_variable": "unsure"
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM