3

is there any way to dynamically create missing keys if i want to want to set a variable in a subdictionary.

essentially I want to create any missing keys and set my value.

self.portdict[switchname][str(neighbor['name'])]['local']['ports'] = []

currently i'm doing it but its messy:

if not switchname in self.portdict:
    self.portdict[switchname] = {}
if not str(neighbor['name']) in self.portdict[switchname]:
    self.portdict[switchname][str(neighbor['name'])] = {}
if not 'local' in self.portdict[switchname][str(neighbor['name'])]:
    self.portdict[switchname][str(neighbor['name'])]['local'] = {}
if not 'ports' in self.portdict[switchname][str(neighbor['name'])]['local']:
    self.portdict[switchname][str(neighbor['name'])]['local']['ports'] = []

Is there any way to do this in one or two lines instead?

4 Answers 4

3

This is easier to do without recursion:

def set_by_path(dct, path, value):
    ipath = iter(path)
    p_last = next(ipath)
    try:
        while True:
            p_next = next(ipath)
            dct = dct.setdefault(p_last, {})
            p_last = p_next
    except StopIteration:
        dct[p_last] = value

And a test case:

d = {}
set_by_path(d, ['foo', 'bar', 'baz'], 'qux')
print d  # {'foo': {'bar': {'baz': 'qux'}}}

If you want to have it so you don't need a function, you can use the following defaultdict factory which allows you to nest things arbitrarily deeply:

from collections import defaultdict

defaultdict_factory = lambda : defaultdict(defaultdict_factory)

d = defaultdict_factory()
d['foo']['bar']['baz'] = 'qux'
print d
Sign up to request clarification or add additional context in comments.

4 Comments

The recursive defaultdict factory is an interesting idea! I hadn't thought of doing that.
That fails when the OP wants a list at the last depth.
@o11c -- Not really. It just means that OP needs to set it explicitly. d['foo']['bar']['baz'] = [] or d['foo']['bar'] = defaultdict(list) which OP has to do for any of the other answers as well...
@mgilson - "which OP has to do for any of the other answers as well..." I don't think you read my answer. The OP would need to specify that he wants a list at the last level, but it's done automatically rather than having to explicitly set it each time. I think that makes a big difference in usability (which is why I wrote it that way for use in my own code).
1

Use collections.defaultdict

self.portdict = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: []))))

3 Comments

Its better to put defaultdict(list) at the end
@matsjoyce I figured it's more obvious this say, since most people don't think of list as a callable
I'd argue that those who do not think of list as callable do not know much about lambdas. But, what ever you think helps the OP.
1

I've run into a similar problem in the past. I found that defaultdict was the right answer for me—but writing the super long definitions (like the one in @o11c's answer or @Apero's answer) was no good. Here's what I came up with instead:

from collections import defaultdict
from functools import partial

def NestedDefaultDict(levels, baseFn):
    def NDD(lvl):
        return partial(defaultdict, NDD(lvl-1)) if lvl > 0 else baseFn
    return defaultdict(NDD(levels-1))

This creates a dictionary with levels of nested dictionaries. So if you have levels=3, then you need 3 keys to access the bottom-level value. The second argument is a function which is used to create the bottom-level values. Something like list or lambda: 0 or even dict would work well.

Here's an example of using the "automatic" keys with 4 levels, and list as the default function:

>>> x = NestedDefaultDict(4, list)
>>> x[1][2][3][4].append('hello')
>>> x
defaultdict(<functools.partial object at 0x10b5c22b8>, {1: defaultdict(<functools.partial object at 0x10b5c2260>, {2: defaultdict(<functools.partial object at 0x10b5c2208>, {3: defaultdict(<type 'list'>, {4: ['hello']})})})})

I think that's basically what you'd want for the case in your question. Your 4 "levels" are switch-name, neighbor-name, local, & ports—and it looks like you want a list at the bottom-level to store your ports.

Another example using 2 levels and lambda: 0 as the default:

>>> y = NestedDefaultDict(2, lambda: 0)
>>> y['foo']['bar'] += 7
>>> y['foo']['baz'] += 10
>>> y['foo']['bar'] += 1
>>> y
defaultdict(<functools.partial object at 0x1021f1310>, {'foo': defaultdict(<function <lambda> at 0x1021f3938>, {'baz': 10, 'bar': 8})})

3 Comments

Cool answer, though IMO the right thing would actually be to introduce additional classes for the middle levels, so that each class just contains one-deep defaultdicts
@o11c - That's an interesting opinion. I would think that adding additional classes is just pointless complexity since you don't actually use the classes for anything but to wrap a defaultdict. On the other hand, I think it would be beneficial to completely replace some levels of defaultdicts with class instances. For example, local and ports seem more like class field names than dynamic dictionary entries, so replacing those to levels with classes that have local and ports fields might be better. Maybe that's what you meant.
Particularly I was thinking that portdict[switchname] should obviously return a Switch, and switch[neighborname] should return a Neighbor
0

Have a close look to collections.defaultdict:

from collections import defaultdict
foo = defaultdict(dict)
foo['bar'] = defaultdict(dict)
foo['bar']['baz'] = defaultdict(dict)
foo['bar']['baz']['aaa'] = 1
foo['bor'] = 0
foo['bir'] = defaultdict(list)
foo['bir']['biz'].append(1)
foo['bir']['biz'].append(2)

print foo
defaultdict(<type 'dict'>, {'bir': defaultdict(<type 'list'>, {'biz': [1, 2]}), 'bor': 0, 'bar': defaultdict(<type 'dict'>, {'baz': defaultdict(<type 'dict'>, {'aaa': 1})})})

1 Comment

I saw some other answer mentionning defaultdict after I posted this one. OP, you decide if this helps understanding defaultdict, otherwise I'll delete it

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.