Let's say i have a list of keys
key_lst = ["key1", "key2", "key3"]
and i have a value
value = "my_value"
and an example dict my_dict with this structure
{
"key1": {
    "key2": {
        "key3": "some_value"
        }
    },
}
How can I dynamically assign the new value in variable value to my_dict["key1"]["key2"]["key3"] by going thru / looping over my key_lst?
I can not just say my_dict["key1"]["key2"]["key3"] = value since the keys and the number of keys is changing. I always get the keys (the path that i have to save the value at) in a list...
The output I am looking for is {'key1': {'key2': {'key3': 'my_value'}}}. The dictionary structure is predefined.
I'm using Python 3.7
functools.reduce
You can define a function using functools.reduce to apply getitem repeatedly and then set a supplied value:
from functools import reduce
from operator import getitem
def set_nested_item(dataDict, mapList, val):
    """Set item in nested dictionary"""
    reduce(getitem, mapList[:-1], dataDict)[mapList[-1]] = val
    return dataDict
key_lst = ["key1", "key2", "key3"]
value = "my_value"
d = {"key1": {"key2": {"key3": "some_value"}}}
d = set_nested_item(d, key_lst, value)
print(d)
# {'key1': {'key2': {'key3': 'my_value'}}}
Note operator.getitem is used to access dict.__getitem__, or its more commonly used syntactic sugar dict[]. In this instance, functools.reduce calls getitem recursively on dataDict, successively using each value in mapList[:-1] as an argument. With [:-1], we intentionally leave out the last value, so we can use __setitem__ via dict[key] = value for the final key.
collections.defaultdict
If you wish to add items at arbitrary branches not yet been defined, you can construct a defaultdict. For this, you can first defaultify your regular dictionary input, then use set_nested_item as before:
from collections import defaultdict
def dd_rec():
    return defaultdict(dd_rec)
def defaultify(d):
    if not isinstance(d, dict):
        return d
    return defaultdict(dd_rec, {k: defaultify(v) for k, v in d.items()})
dd = defaultify(d)
key_lst = ["key1", "key2", "key5", "key6"]
value = "my_value2"
dd = set_nested_item(dd, key_lst, value)
print(dd)
# defaultdict(<function __main__.<lambda>>,
#             {'key1': defaultdict(<function __main__.<lambda>>,
#                          {'key2': defaultdict(<function __main__.<lambda>>,
#                                       {'key3': 'my_value',
#                                        'key5': defaultdict(<function __main__.<lambda>>,
#                                                    {'key6': 'my_value2'})})})})
                        You can iteratively build/access levels using setdefault in a loop:
d = {}
d2 = d
for k in key_lst[:-1]:
    d2 = d2.setdefault(k, {})
d2[key_lst[-1]] = value
print(d)
# {'key1': {'key2': {'key3': 'my_value'}}}
d is the reference to your dictionary, and d2 is a throw-away reference that accesses inner levels at each iteration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With