Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a recursive version of the dict.get() built-in?

I have a nested dictionary object and I want to be able to retrieve values of keys with an arbitrary depth. I'm able to do this by subclassing dict:

>>> class MyDict(dict): ...     def recursive_get(self, *args, **kwargs): ...         default = kwargs.get('default') ...         cursor = self ...         for a in args: ...             if cursor is default: break ...             cursor = cursor.get(a, default) ...         return cursor ...  >>> d = MyDict(foo={'bar': 'baz'}) >>> d {'foo': {'bar': 'baz'}} >>> d.get('foo') {'bar': 'baz'} >>> d.recursive_get('foo') {'bar': 'baz'} >>> d.recursive_get('foo', 'bar') 'baz' >>> d.recursive_get('bogus key', default='nonexistent key') 'nonexistent key' 

However, I don't want to have to subclass dict to get this behavior. Is there some built-in method that has equivalent or similar behavior? If not, are there any standard or external modules that provide this behavior?

I'm using Python 2.7 at the moment, though I would be curious to hear about 3.x solutions as well.

like image 936
jayhendren Avatar asked Jan 29 '15 22:01

jayhendren


People also ask

Can you explain the dict get () function?

get()—method comes in: it returns the value for a specified key in a dictionary. This method will only return a value if the specified key is present in the dictionary, otherwise it will return None.

Is there a built in dictionary in Python?

Creating Python Dictionary While the values can be of any data type and can repeat, keys must be of immutable type (string, number or tuple with immutable elements) and must be unique. As you can see from above, we can also create a dictionary using the built-in dict() function.

Is dict () the same as {}?

The setup is simple: the two different dictionaries - with dict() and {} - are set up with the same number of elements (x-axis). For the test, each possible combination for an update is run.


2 Answers

A very common pattern to do this is to use an empty dict as your default:

d.get('foo', {}).get('bar') 

If you have more than a couple of keys, you could use reduce (note that in Python 3 reduce must be imported: from functools import reduce) to apply the operation multiple times

reduce(lambda c, k: c.get(k, {}), ['foo', 'bar'], d) 

Of course, you should consider wrapping this into a function (or a method):

def recursive_get(d, *keys):     return reduce(lambda c, k: c.get(k, {}), keys, d) 
like image 177
Thomas Orozco Avatar answered Oct 01 '22 20:10

Thomas Orozco


@ThomasOrozco's solution is correct, but resorts to a lambda function, which is only necessary to avoid TypeError if an intermediary key does not exist. If this isn't a concern, you can use dict.get directly:

from functools import reduce  def get_from_dict(dataDict, mapList):     """Iterate nested dictionary"""     return reduce(dict.get, mapList, dataDict) 

Here's a demo:

a = {'Alice': {'Car': {'Color': 'Blue'}}}   path = ['Alice', 'Car', 'Color'] get_from_dict(a, path)  # 'Blue' 

If you wish to be more explicit than using lambda while still avoiding TypeError, you can wrap in a try / except clause:

def get_from_dict(dataDict, mapList):     """Iterate nested dictionary"""     try:         return reduce(dict.get, mapList, dataDict)     except TypeError:         return None  # or some other default value 

Finally, if you wish to raise KeyError when a key does not exist at any level, use operator.getitem or dict.__getitem__:

from functools import reduce from operator import getitem  def getitem_from_dict(dataDict, mapList):     """Iterate nested dictionary"""     return reduce(getitem, mapList, dataDict)     # or reduce(dict.__getitem__, mapList, dataDict) 

Note that [] is syntactic sugar for the __getitem__ method. So this relates precisely how you would ordinarily access a dictionary value. The operator module just provides a more readable means of accessing this method.

like image 27
jpp Avatar answered Oct 01 '22 22:10

jpp