05
Apr
Multi-dimensional dicts in Python & Django?
The code I’m working on frequently uses multi-dimensional dicts in Python. There is a bunch of code that seems to be always doing this:
# a,b, and c have come from some external data source, QuerySet, etc.
a = 'axis_1'
b = 'axis_2'
c = 'axis_3'
d = 'value'
if a not in context:
context[a] = {}
if b not in context:
context[a][b] = {}
if c not in context:
context[a][b][c] = value
Or some variant thereof. Is there a better pattern for multi-dimensional (arbitrary) dicts in Python/Django? The setdefault() method doesn’t have the right semantics for this operation, and I’m not sure that it would actually work right for multi-dimensional arrays anyway.
Python’s accessors don’t quite handle this. I’ve had issues like this before.
The get() method is closer that setdefault() to what you want:
leaf = context.get(a, []).get(b, [])
if c in leaf:
leaf[c] = value
Do you really need Multi-dimensional dicts, or just multiple keys for a single value?
Perhaps you could use a single dict, with tuple values for the keys?
value = context[ (a, b, c) ]
context[ (a, b,c ) ] = value
Yeah, that’s actually a pretty reasonable idea. The problem is that what we’re rendering is a giant set of tables (1st dimension is a <tbody>, 2nd is a <tr> and 3rd is a <td>) So, we need to iterate through each dimension, which isn’t possible with the tuple as the key to the dict. I suspect that in some cases, this is a perfectly acceptable solution.
Brian, the problem with using default values in get() is that they aren’t inserted into the dictionary when fetched, just returned as anonymous temp variables. Any value inserted into the nested dictionary would just be lost.
Slacy, I don’t know of any good generic solutions for what you want. I’d probably just end up writing special purpose helpers for data insertions (reads already work the way you want). Possibly attached to a dict subclass for the outermost dict.
But people inserting data would need to do: context.slacy_insert(a, b, c).
FYI, the answer is defaultdict. See my latest post. If you want a 3-dimensional dict, you can say:
from collections import defaultdict
d = defaultdict(lambda: defaultdict(defaultdict))
d[3][4][5] = 6
And it totally works.