Mark
Mark

Reputation: 2148

Reaching into a nested dictionary several levels deep (that might not exist)

I have an API that I call that returns a dictionary. Part of that dictionary is itself another dictionary. In that inside dictionary, there are some keys that might not exist, or they might. Those keys could reference another dictionary.

To give an example, say I have the following dictionaries:

dict1 = {'a': {'b': {'c':{'d':3}}}}
dict2 = {'a': {'b': {''f': 2}}}

I would like to write a function that I can pass in the dictionary, and a list of keys that would lead me to the 3 in dict1, and the 2 in dict2. However, it is possible that b and c might not exist in dict1, and b and f might not exist in dict2.

I would like to have a function that I could call like this:

get_value(dict1, ['a', 'b', 'c'])

and that would return a 3, or if the keys are not found, then return a default value of 0.

I know that I can use something like this:

val = dict1.get('a', {}).get('b', {}).get('c', 0)

but that seems to be quite wordy to me.

I can also flatten the dict (see https://stackoverflow.com/a/6043835/1758023), but that can be a bit intensive since my dictionary is actually fairly large, and has about 5 levels of nesting in some keys. And, I only need to get two things from the dict.

Right now I am using the flattenDict function in the SO question, but that seems a bit of overkill for my situation.

Upvotes: 2

Views: 4220

Answers (3)

Ryan Haining
Ryan Haining

Reputation: 36802

Without recursion, just iterate through the keys and go down one level at a time. Putting that inside a try/except allows you to handle the missing key case. KeyError will be raised when the key is not there, and TypeError will be raised if you hit the "bottom" of the dict too soon and try to apply the [] operator to an int or something.

def get_value(d, ks):
    for k in ks:
        try:
            d = d[k] # descend one level
        except (KeyError, TypeError):
            return 0 # when any lookup fails, return 0
    return d # return the final element

Upvotes: 3

TheBlackCat
TheBlackCat

Reputation: 10298

You can use a recursive function:

def get_value(mydict, keys):
    if not keys:
        return mydict
    if keys[0] not in mydict:
        return 0
    return get_value(mydict[keys[0]], keys[1:])

If keys can not only be missing, but be other, non-dict types, you can handle this like so:

def get_value(mydict, keys):
    if not keys:
        return mydict
    key = keys[0]
    try:
        newdict = mydict[key]
    except (TypeError, KeyError):
        return 0
    return get_value(newdict, keys[1:])

Upvotes: 3

Cory Kramer
Cory Kramer

Reputation: 117866

Here is a recursive function that should work for general cases

def recursive_get(d, k):
    if len(k) == 0:
        return 0
    elif len(k) == 1:
        return d.get(k[0], 0)       
    else:
        value = d.get(k[0], 0)
        if isinstance(value, dict):
            return recursive_get(value, k[1:])
        else:
            return value

It takes arguments of the dict to search, and a list of keys, which it will check one per level

>>> dict1 = {'a': {'b': {'c':{'d':3}}}}
>>> recursive_get(dict1, ['a', 'b', 'c'])
{'d': 3}

>>> dict2 = {'a': {'b': {'f': 2}}}
>>> recursive_get(dict2, ['a', 'b', 'c'])
0

Upvotes: 0

Related Questions