问题
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
回答1:
Predefined dictionary structure: 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.
Arbitrary dictionary nesting: 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'})})})})
回答2:
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.
回答3:
I guess you can loop through your keys like this :
d = {}
a = d
for i in key_lst:
a[i] = {}
if i == key_lst[-1]:
a[i] = value
else:
a = a[i]
print(d)
# {'key1': {'key2': {'key3': 'my_value'}}}
Edit: I guess I misread the question and answered as if the dictionnary wasn't already existing. jpp answer is pretty neat otherwise I guess!
回答4:
This is what you want:
def update(d, key_lst , val):
for k in key_lst[:-1]:
if k not in d:
d[k] = {}
d = d[k]
d[key_lst[-1]] = val
d = {}
update(d, list('qwer'), 0)
# d = {'q': {'w': {'e': {'r': 0}}}}
You could use defaultdict
too, it's neat in a sense but prints rather ugly...:
from collections import defaultdict
nest = lambda: defaultdict(nest)
d = nest()
def update(d, key_lst , val):
for k in key_lst[:-1]:
d = d[k]
d[key_lst[-1]] = val
update(d, 'qwer', 0)
回答5:
key_lst = ["key1", "key2", "key3"]
my_dict={
"key1": {
"key2": {
"key3": "some_value"
}
},
}
val=my_dict
#loop gets second to last key in chain(path) and assigns it to val
for x in key_lst[:-1]:
val=val[x]
#now we can update value of last key, cause dictionary key is passed by reference
val[key_lst[-1]]="new value"
print (my_dict)
#{'key1': {'key2': {'key3': 'new value'}}}
来源:https://stackoverflow.com/questions/54137991/how-to-update-values-in-nested-dictionary-if-keys-are-in-a-list