I'm trying to programmatically set a value in a dictionary, potentially nested, given a list of indices and a value.
So for example, let's say my list of indices is:
['person', 'address', 'city'] and the value is
'New York' I want as a result a dictionary object like:
{ 'Person': { 'address': { 'city': 'New York' } } Basically, the list represents a 'path' into a nested dictionary.
I think I can construct the dictionary itself, but where I'm stumbling is how to set the value. Obviously if I was just writing code for this manually it would be:
dict['Person']['address']['city'] = 'New York' But how do I index into the dictionary and set the value like that programmatically if I just have a list of the indices and the value?
Python
98 Answers
Something like this could help:
def nested_set(dic, keys, value): for key in keys[:-1]: dic = dic.setdefault(key, {}) dic[keys[-1]] = value And you can use it like this:
>>> d = {} >>> nested_set(d, ['person', 'address', 'city'], 'New York') >>> d {'person': {'address': {'city': 'New York'}}} 4I took the freedom to extend the code from the answer of Bakuriu. Therefore upvotes on this are optional, as his code is in and of itself a witty solution, which I wouldn't have thought of.
def nested_set(dic, keys, value, create_missing=True): d = dic for key in keys[:-1]: if key in d: d = d[key] elif create_missing: d = d.setdefault(key, {}) else: return dic if keys[-1] in d or create_missing: d[keys[-1]] = value return dic When setting create_missing to True, you're making sure to only set already existing values:
# Trying to set a value of a nonexistent key DOES NOT create a new value print(nested_set({"A": {"B": 1}}, ["A", "8"], 2, False)) >>> {'A': {'B': 1}} # Trying to set a value of an existent key DOES create a new value print(nested_set({"A": {"B": 1}}, ["A", "8"], 2, True)) >>> {'A': {'B': 1, '8': 2}} # Set the value of an existing key print(nested_set({"A": {"B": 1}}, ["A", "B"], 2)) >>> {'A': {'B': 2}} Here's another option:
from collections import defaultdict recursivedict = lambda: defaultdict(recursivedict) mydict = recursivedict() I originally got this from here: Set nested dict value and create intermediate keys.
It is quite clever and elegant if you ask me.
0First off, you probably want to look at setdefault.
As a function I'd write it as
def get_leaf_dict(dct, key_list): res=dct for key in key_list: res=res.setdefault(key, {}) return res This would be used as:
get_leaf_dict( dict, ['Person', 'address', 'city']) = 'New York' This could be cleaned up with error handling and such. Also using *args rather than a single key-list argument might be nice; but the idea is that you can iterate over the keys, pulling up the appropriate dictionary at each level.
Here is my simple solution: just write
terms = ['person', 'address', 'city'] result = nested_dict(3, str) result[terms] = 'New York' # as easy as it can be You can even do:
terms = ['John', 'Tinkoff', '1094535332'] # account in Tinkoff Bank result = nested_dict(3, float) result[terms] += 2375.30 Now the backstage:
from collections import defaultdict class nesteddict(defaultdict): def __getitem__(self, key): if isinstance(key, list): d = self for i in key: d = defaultdict.__getitem__(d, i) return d else: return defaultdict.__getitem__(self, key) def __setitem__(self, key, value): if isinstance(key, list): d = self[key[:-1]] defaultdict.__setitem__(d, key[-1], value) else: defaultdict.__setitem__(self, key, value) def nested_dict(n, type): if n == 1: return nesteddict(type) else: return nesteddict(lambda: nested_dict(n-1, type)) The dotty_dict library for Python 3 can do this. See documentation, Dotty Dict for more clarity.
from dotty_dict import dotty dot = dotty() string = '.'.join(['person', 'address', 'city']) dot[string] = 'New York' print(dot) Output:
{'person': {'address': {'city': 'New York'}}} Use these pair of methods
def gattr(d, *attrs): """ This method receives a dict and list of attributes to return the innermost value of the give dict """ try: for at in attrs: d = d[at] return d except: return None def sattr(d, *attrs): """ Adds "val" to dict in the hierarchy mentioned via *attrs For ex: sattr(animals, "cat", "leg","fingers", 4) is equivalent to animals["cat"]["leg"]["fingers"]=4 This method creates necessary objects until it reaches the final depth This behaviour is also known as autovivification and plenty of implementation are around This implementation addresses the corner case of replacing existing primitives """ for attr in attrs[:-2]: # If such key is not found or the value is primitive supply an empty dict if d.get(attr) is None or isinstance(d.get(attr), dict): d[attr] = {} d = d[attr] d[attrs[-2]] = attrs[-1] 1Here's a variant of Bakuriu's answer that doesn't rely on a separate function:
keys = ['Person', 'address', 'city'] value = 'New York' nested_dict = {} # Build nested dictionary up until 2nd to last key # (Effectively nested_dict['Person']['address'] = {}) sub_dict = nested_dict for key_ind, key in enumerate(keys[:-1]): if not key_ind: # Point to newly added piece of dictionary sub_dict = nested_dict.setdefault(key, {}) else: # Point to newly added piece of sub-dictionary # that is also added to original dictionary sub_dict = sub_dict.setdefault(key, {}) # Add value to last key of nested structure of keys # (Effectively nested_dict['Person']['address']['city'] = value) sub_dict[keys[-1]] = value print(nested_dict) >>> {'Person': {'address': {'city': 'New York'}}}