clay
clay

Reputation: 20470

How to get a value from a nested dict?

Is there an easy, concise way to get a value from a nested dict and get None if it's not there?

d1 = None
d2 = {}
d3 = {"a": {}}
d4 = {"a": {"b": 12345}}
ds = [d1, d2, d3, d4]


def nested_get(d):
    # Is there a simpler concise one-line way to do exactly this, query a nested dict value, and return None if
    # it doesn't exist?
    a_val = d.get("a") if d else None
    b_val = a_val.get("b") if a_val else None
    return b_val

if __name__ == "__main__":
    bs = [nested_get(d) for d in ds]
    print("bs={}".format(bs))

Upvotes: 2

Views: 313

Answers (4)

prschmid
prschmid

Reputation: 498

If you'd rather specify your "path" into the nested dict as a string path (like a directory) rather than a set of individual keys, this has worked for our needs:

def deep_get(d, path, default=None):
    """Get a deeply nested value in a dict based on multiple keys

    :param d: The dictionary
    :param path: The key path to search separated by '/'
    :param default: The default value to return if there is nothing
    :returns: The value at the given path or the default
    """
    dd = d
    split = path.split("/")
    for i, key in enumerate(split):
        try:
            dd = dd.get(key, {})
        except AttributeError as exc:
            p = "/".join(split[0:i])
            raise AttributeError("{} at {}".format(exc.message, p)), \
                None, sys.exc_info()[2]
    return dd or default

where the usage is as simple as

d = {
  'a': {
    'b': {
      'c': {
        'd': "hi!"
      }
    }
  }
}

print deep_get(d, 'a/b/c/d')

Upvotes: 0

Jared Goguen
Jared Goguen

Reputation: 9008

If you want to get a little dirty, you can create a custom class that extends how a typical dictionary handles subscripting.

class QueryDict(dict):
    def __getitem__(self, keys):
        current = self
        try:
            for key in keys:
                current = dict.__getitem__(current, key)
            return current
        except (TypeError, KeyError):
            return None


d = {"a": {"b": {"c": 12345}}}
d = QueryDict(d)
print d['a','b','c'] # 12345
print d['a,c,e'] # None

Or, if you're trying to dynamically call entries, it might be better to allow keys to be passed in by a comma-separated string.

class QueryDict(dict):
    def __getitem__(self, key_string):
        current = self
        try:
            for key in key_string.split(','):
                current = dict.__getitem__(current, key)
            return current
        except (TypeError, KeyError):
            return None


d = {"a": {"b": {"c": 12345}}}
d = QueryDict(d)
print d['a,b,c'] # 12345.
print d['a,c,e'] # None

Upvotes: 1

clay
clay

Reputation: 20470

OK, using a simple custom function, I can do this, as a general concise solution:

d1 = None
d2 = {}
d3 = {"a": {}}
d4 = {"a": {"b": 12345}}
ds = [d1, d2, d3, d4]


def get_nested_dict_value(d, *args):
    for a in args:
        d = d.get(a) if d else None
    return d


if __name__ == "__main__":
    bs = [get_nested_dict_value(d, "a", "b") for d in ds]
    print("bs={}".format(bs))

Upvotes: 1

Alyssa Haroldsen
Alyssa Haroldsen

Reputation: 3731

This should work:

def nested_get(d, *keys):
    for key in keys:
        if d is None:
            break
        d = d.get(key)
    return d

Upvotes: 1

Related Questions