9

If I have an element I'm trying to get from a dictionary: my_dict[i]['level_1']['level_2']['my_var'].

Is there a cleaner way than doing this to check for nulls?

if 'level_1' in my_dict[i]:
    if 'level_2' in my_dict[i]['level_1']:
        if 'my_var' in my_dict[i]['level_1']['level_2']:
            my_var = my_dict[i]['level_1']['level_2']['my_var']
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
Jeanne Lane
  • 495
  • 1
  • 9
  • 26
  • 8
    You can use `dict.get` - `my_dict[i].get('level_1', {}).get('level_2', {}).get('my_var')`. It will be `None` if not found. – jonrsharpe Jan 09 '17 at 17:37
  • 7
    or a `try-except KeyError` block – Ma0 Jan 09 '17 at 17:38
  • 4
    Your approach here will depend entirely on what you are trying to accomplish and your desired behavior. A simple `my_var = my_dict[i]['level_1']['level_2']['my_var']` wrapped in `try-except` might be the way to go, or what jonsharpe proposed, depending on the nature of you data. – juanpa.arrivillaga Jan 09 '17 at 17:38
  • 2
    @Ev.Kounis perhaps `except (KeyError, TypeError):` which will work in case one of the entries exist but contains None instead of a dictionary. – juanpa.arrivillaga Jan 09 '17 at 17:40
  • Check this: http://stackoverflow.com/questions/14692690/access-nested-dictionary-items-via-a-list-of-keys – Mohammad Yusuf Jan 09 '17 at 17:43
  • https://github.com/akesterson/dpath-python – Mohammad Yusuf Jan 09 '17 at 17:46

2 Answers2

7

You can simply define your own:

def get_deep(dic,*keys,default=None):
    for key in keys:
        if isinstance(dic,dict) and key in dic:
            dic = dic[key]
        else:
            return default
    return dic

Or if you want to test:

def in_deep(dic,*keys):
    for key in keys:
        if isinstance(dic,dict) and key in dic:
            dic = dic[key]
        else:
            return False
    return True

You can use this code with in_deep(my_dict[i],'level1','level2','my_var') and it will return True if it can indeed get that deep in the dictionary. If you want to obtain the element that deep, you can call get_deep(my_dict[i],'level1','level2','my_var'). get_deep will return the default parameter (by default None) in case it cannot reach the path.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
3

Here is my one-liner:

# returns False or the value of the end element
reduce(lambda x, y: x and y in x and x[y], list_of_keys, target_dictionary)

Example:

a = {'b': {'c': {'d': True}}}
reduce(lambda x, y: x and y in x and x[y], ['b', 'c', 'd'], a)  # True
reduce(lambda x, y: x and y in x and x[y], ['b', 'cd', 'd'], a)  # False

How it works: at every iteration of reduce, it checks that previouis keys were in the target dictionary (x). If not, it will return False because of and condition immediately and propagate it down the list. Then, it will check if the requested key is in the dict (x in y). If it is, it will pass the lower level dictionary to the lower level as x.

Note: the last element (a['b']['c']['d']) has to evaluate to True. If you expect False at the end, check the last level explicitly: 'd' in reduce(...)

Marat
  • 15,215
  • 2
  • 39
  • 48