pythondictionarymethodsexcept

Safe method to get value of nested dictionary


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

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

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


Solution

  • 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 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 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')