4

Having a dict like this

my_pets = {
    'Rudolf': {
        'animal': 'cat', 
        'legs': 4
    }
}

What is the cleaner way of achieving below equivalent?

my_pets['Rudolf']['legs']['front-right']['injured'] = True
my_pets['Rudolf']['legs']['front-left']['injured'] = False

And it should update as

my_pets = {
    'Rudolf': {
        'animal': 'cat', 
        'legs': {
            'front-right': {'injured':True},
            'front-left': {'injured':False}
        }
    }
}
4
  • I think you want to make a few classes here, instead of putting all your data in nested dicts. That's the only way I can think of to make it "cleaner". Note: with your current data representation, your way is the cleanest possible way to access Commented Nov 12, 2015 at 2:20
  • Edited the question slightly, The ugliness I am into now is, I had to manually chain them by checking for their existence and creating empty dicts on non-existence and move to next depth. Commented Nov 12, 2015 at 2:25
  • Use a collections.defaultdict(lambda: collections.defaultdict) instead of a dict. That might help with the updating process Commented Nov 12, 2015 at 2:26
  • @inspectorG4dget That's the right direction, but won't work past one level, since accessing a missing key at the top level creates a defaultdict without an initialized default factory. So whenever you try and access a missing key at the second level, a KeyError is raised. Commented Nov 12, 2015 at 3:05

5 Answers 5

7

You could create an "infinite" defaultdict, as follows:

from collections import defaultdict

def infinidict():
    return defaultdict(infinidict)

Then writing:

>>> my_pets = infinidict()
>>> my_pets['Rudolf']['animal'] = 'cat'
>>> my_pets['Rudolf']['weight'] = 3
>>> my_pets['Rudolf']['legs']['front-right']['injured'] = True
>>> my_pets
defaultdict(<function __main__.infinidict>,
            {'Rudolf': defaultdict(<function __main__.infinidict>,
                         {'animal': 'cat',
                          'legs': defaultdict(<function __main__.infinidict>,
                                      {'front-right': defaultdict(<function __main__.infinidict>,
                                                   {'injured': True})}),
                          'weight': 3})})

The output looks messy, but my_pets can be used wherever a dict is required.

Sign up to request clarification or add additional context in comments.

1 Comment

Neat trick. I always considered the "limited depth" of defaultdict to be a big drawback, did not think of doing something like this.
6

Below is a dictionary subclass which is lenient to missing keys up to an arbitrary depth:

class freedict(dict):
    # called when trying to read a missing key
    def __missing__(self, key):
        self[key] = freedict()
        return self[key]

    # called during attribute access
    # note that this invokes __missing__ above
    def __getattr__(self, key):
        return self[key]

    # called during attribute assignment
    def __setattr__(self, key, value):
        self[key] = value

This can be used like so (attribute access to keys is a personal preference):

d = freedict()
d['one']['two']['three'] = 1
d.one.two.three = 2

5 Comments

Pretty clean, and I think preferable to recursive defaultdicts, since printing an instance looks just like what you'd get with a dict.
Sounds neat, but might break in few cases, For ex: d = freedict()⏎ d['one'] = 1⏎ d['one']['two']['three'] = 1
@itsneo true, but what else would you expect to happen in this scenario? I'd argue that it should break.
I suppose you could code up a tree data structure which can hold values at internal nodes. But at that point, it's not really a dict anymore, and it would probably need to have a special way of accessing values. I don't think an API like what you wrote in the comment would actually be possible, although I will think more about it.
@igor, I do get you, Yet in the breaking case above we can expect d['one']['two']['three'] = 1 to produce {'one': {'two': {'three': 1}}} which is what your original code produces. However if d['one'] is already presented it's unable to replace them. Remember how a normal dict assignment works, It simply replaces existing value, which doesn't happen here.
4

This is a very interesting and a very practical situation one can encounter. There are numerous implementations, each of which solve certain problems but miss out on a few edge cases.

Possible solutions and varying answers can be found in these titles.

What is the best way to implement nested dictionaries?

What's the best way to initialize a dict of dicts in Python?

Set nested dict value and create intermediate keys

Also, there are numerous gists and blogs found on this requirement 'autovivification', including a wikipedia presence.

http://blog.yjl.im/2013/08/autovivification-in-python.html

https://news.ycombinator.com/item?id=3881171

https://gist.github.com/hrldcpr/2012250

https://en.wikipedia.org/wiki/Autovivification

http://blogs.fluidinfo.com/terry/2012/05/26/autovivification-in-python-nested-defaultdicts-with-a-specific-final-type/

While the above implementations are handy once, edge cases can still be problematic. At the time of this writing, no implementation has handled well whether there is a primitive sitting and blocking the nest.

Here are the 3 main ways this question and related questions are answered here in StackOverflow.

  • Write a helper method, that accepts a dictionary, value and list of nested keys. Works well with plain dict objects, but lacks the usual square bracket syntax.

  • Use Defaultdict and write a custom class. Fundamentally this works since default dict supplies {} for missing keys. Great syntax, but works only for the objects that were created using the custom class.

  • Use tuples to store and retrieve (https://stackoverflow.com/a/651930/968442). The Worst idea of all. Should not even be claimed as a solution. Here is why

    mydict = {}
    mydict['foo', 'bar', 'baz'] = 1
    print mydict['foo', 'bar', 'baz']

    Will work fine, but when you access mydict['foo', 'bar'] the expectation will be {'baz':1}, not a KeyError. This basically destroys the idea of iterable & nested structure.

Of the three approaches, my bet goes to option 1. By writing a tiny helper method the edge cases can be resolved pragmatically, here is my implementation.

def sattr(d, *attrs):
    # Adds "val" to dict in the hierarchy mentioned via *attrs
    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 not isinstance(d.get(attr), dict):
            d[attr] = {}
        d = d[attr]
    d[attrs[-2]] = attrs[-1]

Now

my_pets = {'Rudolf': {'animal': 'cat', 'legs': 4}}
sattr(my_pets, 'Rudolf', 'legs', 'front-right', 'injured', True)
sattr(my_pets, 'Rudolf', 'legs', 'front-left', 'injured', False)

will produce

{'Rudolf': {'animal': 'cat', 'legs': 4}}
{'Rudolf': {'animal': 'cat', 'legs': {'front-right': {'injured': True}}}}
{'Rudolf': {'animal': 'cat', 'legs': {'front-right': {'injured': True},  'front-left': {'injured': False}}}}

Comments

0

Try using try

try:

    # checks whether 'front-right' exists. If exists assigns value. Else raises   exception
    my_pets['Rudolf']['legs']['front-right']= {'injured':True}}

except:

    # On raising exception add 'front-right' to 'legs'
    my_pets['Rudolf']['legs'] = {'front-right': {'injured':True}}

this should work

Comments

0

This will allow you to add a key at any any depth to a dict based on list of the keys.

  def add_multi_key(subscripts, _dict={}, val=None):
        """Add an arbitrary length key to a dict.

        Example:

            out = add_multi_key(['a','b','c'], {}, 1 )

            out -> {'a': {'b': {'c':1}}}

        Arguments:
            subscripts, list of keys to add
            _dict, dict to update. Default is {}
            val, any legal value to a key. Default is None.

        Returns:
            _dict - dict with added key.
        """
        if not subscripts:
            return _dict
        subscripts = [s.strip() for s in subscripts]
        for sub in subscripts[:-1]:
            if '_x' not in locals():
                if sub not in _dict:
                    _dict[sub] = {}
                _x = _dict.get(sub)
            else:
                if sub not in _x:
                    _x[sub] = {}
                _x = _x.get(sub)
        _x[subscripts[-1]] = val
        return _dict

Comments

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.