Reputation: 13642
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}
}
}
}
Upvotes: 3
Views: 3849
Reputation: 13642
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?
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
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}}}}
Upvotes: 3
Reputation: 1319
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
Upvotes: 0
Reputation: 15240
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
Upvotes: 5
Reputation: 20695
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.
Upvotes: 6
Reputation: 1650
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
Upvotes: 0