Safe method to get value of nested dictionary
You could use get
twice:
example_dict.get('key1', {}).get('key2')
This will return None
if either key1
or key2
does not exist.
Note that this could still raise an AttributeError
if example_dict['key1']
exists but is not a dict (or a dict-like object with a get
method). The try..except
code you posted would raise a TypeError
instead if example_dict['key1']
is unsubscriptable.
Another difference is that the try...except
short-circuits immediately after the first missing key. The chain of get
calls does not.
If you wish to preserve the syntax, example_dict['key1']['key2']
but do not want it to ever raise KeyErrors, then you could use the Hasher recipe:
class Hasher(dict): # https://stackoverflow.com/a/3405143/190597 def __missing__(self, key): value = self[key] = type(self)() return valueexample_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 a Hasher in much the same way you could use a dict
. All the same methods and syntax is available, Hashers just treat missing keys differently.
You can convert a regular dict
into a Hasher
like this:
hasher = Hasher(example_dict)
and convert a Hasher
to a regular dict
just as easily:
regular_dict = dict(hasher)
Another alternative is to hide the ugliness in a helper function:
def safeget(dct, *keys): for key in keys: try: dct = dct[key] except KeyError: return None return dct
So the rest of your code can stay relatively readable:
safeget(example_dict, 'key1', 'key2')
You could also use python reduce:
def deep_get(dictionary, *keys): return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
By combining all of these answer here and small changes that I made, I think this function would be useful. its safe, quick, easily maintainable.
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>>>