Python safe method for getting nested dictionary value

I have a nested dictionary. Is there only one way to safely derive values?

try: example_dict['key1']['key2'] except KeyError: pass 

Or maybe Python has a method like get() for a nested dictionary?

+93
python dictionary methods except
Sep 14 '14 at 13:11
source share
11 answers

You can use get twice:

 example_dict.get('key1', {}).get('key2') 

This will return None if key1 or key2 does not exist.

Note that this can still raise an AttributeError if example_dict['key1'] exists but is not a dict (or a dictated object with a get method). You sent try..except code you sent instead of TypeError if example_dict['key1'] not a subscription.

Another difference is that the try...except short circuit is immediately after the first missing key. The get call chain does not work.




If you want to keep the syntax example_dict['key1']['key2'] , but don't want it to ever raise KeyErrors, you can use the Hasher recipe :

 class Hasher(dict): # /questions/35890/generating-dictionary-keys-on-the-fly/261439#261439 def __missing__(self, key): value = self[key] = type(self)() return value example_dict = Hasher() print(example_dict['key1']) # {} print(example_dict['key1']['key2']) # {} print(type(example_dict['key1']['key2'])) # <class '__main__.Hasher'> 

Note that this returns an empty Hasher when a key is missing.

Since Hasher is a subclass of dict , you can use Hasher in the same way you could use dict . All the same methods and syntax are available; Hashers simply handle missing keys differently.

You can convert a regular dict to Hasher as follows:

 hasher = Hasher(example_dict) 

and converting a Hasher to a regular dict just as easy:

 regular_dict = dict(hasher) 



Another alternative is to hide the ugliness in the helper function:

 def safeget(dct, *keys): for key in keys: try: dct = dct[key] except KeyError: return None return dct 

Thus, the rest of your code can remain relatively readable:

 safeget(example_dict, 'key1', 'key2') 
+179
Sep 14 '14 at 13:17
source share

You can also use python reduce :

 def deep_get(dictionary, *keys): return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary) 
+45
Mar 21 '16 at 13:11
source share

By combining all of these answers here and the small changes that I made, I think this feature will be useful. Its safe, fast, easy to repair.

 def deep_get(dictionary, keys, default=None): return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary) 

Example:

 >>> from functools import reduce >>> def deep_get(dictionary, keys, default=None): ... return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary) ... >>> person = {'person':{'name':{'first':'John'}}} >>> print (deep_get(person, "person.name.first")) John >>> print (deep_get(person, "person.name.lastname")) None >>> print (deep_get(person, "person.name.lastname", default="No lastname")) No lastname >>> 
+11
Oct 23 '17 at 13:41 on
source share

Based on Yoav's answer, an even safer approach:

 def deep_get(dictionary, *keys): return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary) 
+10
Nov 18 '16 at 11:23
source share

While the pruning approach is neat and short, I think a simple loop is easier to iterate over. I also enabled the default option.

 def deep_get(_dict, keys, default=None): for key in keys: if isinstance(_dict, dict): _dict = _dict.get(key, default) else: return default return _dict 

As an exercise to understand how a single-line shot works, I did the following. But in the end, the loopback approach seems more intuitive to me.

 def deep_get(_dict, keys, default=None): def _reducer(d, key): if isinstance(d, dict): return d.get(key, default) return default return reduce(_reducer, keys, _dict) 

Using

 nested = {'a': {'b': {'c': 42}}} print deep_get(nested, ['a', 'b']) print deep_get(nested, ['a', 'b', 'z', 'z'], default='missing') 
+6
Apr 25 '17 at 22:08
source share

Recursive solution. It is not the most efficient, but I find it more readable than other examples, and it does not depend on functools.

 def deep_get(d, keys): if not keys or d is None: return d return deep_get(d.get(keys[0]), keys[1:]) 

example

 d = {'meta': {'status': 'OK', 'status_code': 200}} deep_get(d, ['meta', 'status_code']) # => 200 deep_get(d, ['garbage', 'status_code']) # => None 



More polished version

 def deep_get(d, keys, default=None): """ Example: d = {'meta': {'status': 'OK', 'status_code': 200}} deep_get(d, ['meta', 'status_code']) # => 200 deep_get(d, ['garbage', 'status_code']) # => None deep_get(d, ['meta', 'garbage'], default='-') # => '-' """ assert type(keys) is list if d is None: return default if not keys: return d return deep_get(d.get(keys[0]), keys[1:], default) 
+5
May 4 '18 at 10:48
source share

to get the second level key, you can do this:

 key2_value = (example_dict.get('key1') or {}).get('key2') 
+3
Jun 30 '17 at 3:43 on
source share

A simple class that can wrap a dict and get based on a key:

 class FindKey(dict): def get(self, path, default=None): keys = path.split(".") val = None for key in keys: if val: if isinstance(val, list): val = [v.get(key, default) if v else None for v in val] else: val = val.get(key, default) else: val = dict.get(self, key, default) if not val: break return val 

For example:

 person = {'person':{'name':{'first':'John'}}} FindDict(person).get('person.name.first') # == 'John' 

If the key does not exist, it returns None by default. You can override this using the default= key in the FindDict wrapper - for example, `:

 FindDict(person, default='').get('person.name.last') # == doesn't exist, so '' 
+3
Jul 01 '17 at 10:00
source share

After looking at this to get deep attributes, I did the following to safely get nested dict values ​​using dot notation. This works for me, because my dicts are deserialized MongoDB objects, so I know that the key names do not contain . s. Also, in my context, I can specify a false return value ( None ) that I do not have in my data, so I can avoid the try / except pattern when calling the function.

 from functools import reduce # Python 3 def deepgetitem(obj, item, fallback=None): """Steps through an item chain to get the ultimate value. If ultimate value or path to value does not exist, does not raise an exception and instead returns `fallback`. >>> d = {'snl_final': {'about': {'_icsd': {'icsd_id': 1}}}} >>> deepgetitem(d, 'snl_final.about._icsd.icsd_id') 1 >>> deepgetitem(d, 'snl_final.about._sandbox.sbx_id') >>> """ def getitem(obj, name): try: return obj[name] except (KeyError, TypeError): return fallback return reduce(getitem, item.split('.'), obj) 
+2
Jul 27 '16 at 21:18
source share

Adapting the unutbu answer, which I found useful in my own code:

 example_dict.setdefaut('key1', {}).get('key2') 

It generates a dictionary entry for key1 if it does not already have that key so that you avoid KeyError. If you want to end up creating a dictionary that includes a keyboard shortcut like me, this seems like the easiest solution.

0
Apr 05 '18 at 6:19 06:19
source share

Since creating a key error, if one of the keys is missing, is a reasonable thing, we may not even check it and get it as the only one:

 def get_dict(d, kl): cur = d[kl[0]] return get_dict(cur, kl[1:]) if len(kl) > 1 else cur 
0
Aug 17 '18 at 21:26
source share



All Articles