views:

80

answers:

3

Working with deeply nested python dicts, I would like to be able to assign values in such a data structure like this:

  mydict[key][subkey][subkey2]="value"

without having to check that mydict[key] etc. are actually set to be a dict, e.g. using

  if not key in mydict: mydict[key]={}

The creation of subdictionaries should happen on the fly. What is the most elegant way to allow something equivalent - maybe using decorators on the standard <type 'dict'>?

+6  A: 

You could use a tuple as the key for the dict and then you don't have to worry about subdictionaries at all:

mydict[(key,subkey,subkey2)] = "value"

Alternatively, if you really need to have subdictionaries for some reason you could use collections.defaultdict.

For two levels this is straightforward:

>>> from collections import defaultdict
>>> d = defaultdict(dict)
>>> d['key']['subkey'] = 'value'
>>> d['key']['subkey']
'value'

For three it's slightly more complex:

>>> d = defaultdict(lambda: defaultdict(dict))
>>> d['key']['subkey']['subkey2'] = 'value'
>>> d['key']['subkey']['subkey2']
'value'

Four and more levels are left as an exercise for the reader. :-)

Dave Webb
Amazing. I'm glad I asked, if only because I don't understand how I could miss this. :)
relet
Good answer. Can you provide how to do it with a `defaultdict`? Nesting once is easy: `mydict = defaultdict(dict)`. But is there an elegant solution for nesting twice?
jellybean
@jellybean - just added the three level solution; it's not too bad.
Dave Webb
Hmm, the tuple solution does break the ability to iterate over the subdictionaries though, doesn't it?
relet
@relet - it does which is why I also gave the `defaultdict` solution too.
Dave Webb
+8  A: 
class D(dict):
    def __missing__(self, key):
        self[key]=D()
        return self[key]

d=D()
d['a']['b']['c']=3
gnibbler
You have to be careful with this. `d['a'] = 2 d['a']['b'] = 2` will fail.
unholysampler
Yes, but that is implied in my question - I am asking for a mixed type of dicts and values.
relet
+2  A: 

I like Dave's answer better, but here's an alternative.

from collections import defaultdict
d = defaultdict(lambda : defaultdict(int))
>>> d['a']['b'] += 1
>>> d
defaultdict(<function <lambda> at 0x652f0>, {'a': defaultdict(<type 'int'>, {'b': 1})})
>>> d['a']['b']
1

http://tumble.philadams.net/post/85269428/python-nested-defaultdicts

It's definitely not pretty to have to use lambdas to implements the inner defaulted collections, but apparently necessary.

I82Much
Lambdas are never necessary: you can always use a named function instead. In this case using a named function would at least mean the `repr` has something a bit more meaningful than `lambda`.
Duncan