Chained, nested dict() get calls in python

I'm interrogating a nested dictionary using the dict.get('keyword') method. Currently my syntax is...

M = cursor_object_results_of_db_query

for m in M:
    X = m.get("gparents").get("parent").get("child")
    for x in X:
        y = x.get("key")

However, sometimes one of the "parent" or "child" tags doesn't exist, and my script fails. I know using get() I can include a default in the case the key doesn't exist of the form...

get("parent", '') or
get("parent", 'orphan') 

But if I include any Null, '', or empty I can think of, the chained .get("child") fails when called on ''.get("child") since "" has no method .get().

The way I'm solving this now is by using a bunch of sequential try-except around each .get("") call, but that seems foolish and unpython---is there a way to default return "skip" or "pass" or something that would still support chaining and fail intelligently, rather than deep-dive into keys that don't exist?

Ideally, I'd like this to be a list comprehension of the form:

[m.get("gparents").get("parent").get("child") for m in M]

but this is currently impossible when an absent parent causes the .get("child") call to terminate my program.


Since these are all python dicts and you are calling the dict.get() method on them, you can use an empty dict to chain:

[m.get("gparents", {}).get("parent", {}).get("child") for m in M]

By leaving off the default for the last .get() you fall back to None. Now, if any of the intermediary keys is not found, the rest of the chain will use empty dictionaries to look things up, terminating in .get('child') returning None.

Another approach is to recognize that if the key isn't found, dict.get returns None. However, None doesn't have an attribute .get, so it will throw an AttributeError:

for m in M:
       X = m.get("gparents").get("parent").get("child")
    except AttributeError:

    for x in X:
        y = x.get("key")
        #do something with `y` probably???

Just like Martijn's answer, this doesn't guarantee that X is iterable (non-None). Although, you could fix that by making the last get in the chain default to returning an empty list:

    X = m.get("gparents").get("parent").get("child",[])
 except AttributeError:

Finally, I think that probably the best solution to this problem is to use reduce:

    X = reduce(dict.__getitem__,["gparents","parent","child"],m)
except (KeyError,TypeError):
    for x in X:
       #do something with x

The advantage here is that you know if any of the gets failed based on the type of exception that was raised. It's possible that a get returns the wrong type, then you get a TypeError. If the dictionary doesn't have the key however, it raises a KeyError. You can handle those separately or together. Whatever works best for your use case.

How about using a small helper function?

def getn(d, path):
    for p in path:
        if p not in d:
            return None
        d = d[p]
    return d

and then

[getn(m, ["gparents", "parent", "child"]) for m in M]

I realise I'm a bit late for the part but here's the solution I came up with when faced with a similar problem:

def get_nested(dict_, *keys, default=None):
    if dict_ is None:
        return default
    elem = dict_.get(keys[0], default)
    if len(keys) == 1:
        return elem
    return get_nested(elem, *keys[1:], default=default)

For example:

In [29]: a = {'b': {'c': 1}}
In [30]: get_nested(a, 'b', 'c')
Out[30]: 1
In [31]: get_nested(a, 'b', 'd') is None
Out[31]: True

Need Your Help

noty jquery confirmation and php execution

php jquery

I am using the noty jquery notification in one app that i am currently working on. It provides a "confirm" function but i would like to execute something in php when ...

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.