2

I have the following dictionary:

my_dict = {'key1': {'key2': {'foo': 'bar'} } }

and I would like to append an entry to key1->key2->key3 with value 'blah' yielding:

my_dict = {'key1': {'key2': {'foo': 'bar', 'key3': 'blah'} } }

I am looking for a generic solution that is independent of the number of keys, i.e. key1->key2->key3->key4->key5 should work as well, even though keys from key3 on downwards do not exist. So that I get:

my_dict = {'key1': {'key2': {'foo': 'bar', 'key3': {'key4': {'key5': 'blah'} } } } }

Thanks in advance.

ezdazuzena
  • 6,120
  • 6
  • 41
  • 71
  • possible duplicate of [Creating dictionary using list/tuple elements as key](http://stackoverflow.com/questions/21024982/creating-dictionary-using-list-tuple-elements-as-key) – Martijn Pieters Apr 11 '14 at 11:20
  • The techniques in my answer there apply here too; use `reduce()` to walk to the innermost dictionary (creating additional dictionaries as needed). – Martijn Pieters Apr 11 '14 at 11:21
  • @MartijnPieters: Thanks for the hint. But I think this is only the first half of my problem. Now, after creating the key, how do I access it given the list of keys and the value? – ezdazuzena Apr 11 '14 at 12:50
  • 1
    `target = reduce(lambda d, k: d.setdefault(k, {}), path[:-1], my_dict)` is the path to the right dictionary (since `path[-1]` is just the list key to the actual value), then `target[path[-1]] = 'blah'` does the last step. – Martijn Pieters Apr 11 '14 at 12:53
  • Similar answer using `reduce()` to traverse objects and set attributes instead of dictionaries and keys: [How to use string formatting to dynamically assign variables](http://stackoverflow.com/a/22349385) – Martijn Pieters Apr 11 '14 at 12:54
  • @MartijnPieters OK, reduce is on this weekends to-read list ;) Thanks. If you put it into an answer, you'll get the check. I think it is a slightly different questions. – ezdazuzena Apr 11 '14 at 13:02

2 Answers2

12

You can use the reduce() function to traverse a series of nested dictionaries:

def get_nested(d, path):
    return reduce(dict.__getitem__, path, d)

Demo:

>>> def get_nested(d, path):
...     return reduce(dict.__getitem__, path, d)
... 
>>> my_dict = {'key1': {'key2': {'foo': 'bar', 'key3': {'key4': {'key5': 'blah'}}}}}
>>> get_nested(my_dict, ('key1', 'key2', 'key3', 'key4', 'key5'))
'blah'

This version throws an exception when a key doesn't exist:

>>> get_nested(my_dict, ('key1', 'nonesuch'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in get_nested
KeyError: 'nonesuch'

but you could replace dict.__getitem__ with lambda d, k: d.setdefault(k, {}) to have it create empty dictionaries instead:

def get_nested_default(d, path):
    return reduce(lambda d, k: d.setdefault(k, {}), path, d)

Demo:

>>> def get_nested_default(d, path):
...     return reduce(lambda d, k: d.setdefault(k, {}), path, d)
... 
>>> get_nested_default(my_dict, ('key1', 'nonesuch'))
{}
>>> my_dict
{'key1': {'key2': {'key3': {'key4': {'key5': 'blah'}}, 'foo': 'bar'}, 'nonesuch': {}}}

To set a value at a given path, traverse over all keys but the last one, then use the final key in a regular dictionary assignment:

def set_nested(d, path, value):
    get_nested_default(d, path[:-1])[path[-1]] = value

This uses the get_nested_default() function to add empty dictionaries as needed:

>>> def set_nested(d, path, value):
...     get_nested_default(d, path[:-1])[path[-1]] = value
... 
>>> my_dict = {'key1': {'key2': {'foo': 'bar'}}}
>>> set_nested(my_dict, ('key1', 'key2', 'key3', 'key4', 'key5'), 'blah')
>>> my_dict
{'key1': {'key2': {'key3': {'key4': {'key5': 'blah'}}, 'foo': 'bar'}}}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
1

An alternative to Martijn Pieters's excellent answer would be to use a nested defaultdict, rather than a regular dictionary:

from collections import defaultdict

nested = lambda: defaultdict(nested) # nested dictionary factory

my_dict = nested()

You can set values by using regular nested dictionary access semantics, and empty dictionaries will be created to fill the middle levels as necessary:

my_dict["key1"]["key2"]["key3"] = "blah"

This of course requires that the number of keys be known in advance when you write the code to set the value. If you want to be able to handle a variable-length list of keys, rather than a fixed number, you'll need functions to do the getting and setting for you, like in Martijn's answer.

Blckknght
  • 100,903
  • 11
  • 120
  • 169