Looking for a way to accept or reject a dictionary trail w/o excepting

37 Views Asked by At

I want to do this:

    foo23 = base["foo23"]["subfoo"]["subsubfoo"]
    print(foo23)
    [2,3]

    foo23 = base["noexist"]["nothereeither"]["nope"]
    print(foo23)
    None

I can't seem to accomplish this, using defaultdict and specialized dictionaries. The failed access of the first call can return a 'None', but then the following fields cause an exception for it not being subscriptable. Just wondering if this is possible.

2

There are 2 best solutions below

3
JonSG On BEST ANSWER

If you wanted to walk a tree by a list of keys where the tree might have nodes that where dictionaries or lists or values then you might do:

def find_value_by_keys(root, list_of_keys):
    for key in list_of_keys:
        try:
            root = root[key]
        except KeyError:
            return None
    return root

root = {
    "foo": {"bar": "baz"},
    "foo2": ["bar", "baz"]
}
print(find_value_by_keys(root, ["foo", "bar"]))
print(find_value_by_keys(root, ["foo2", 1]))
print(find_value_by_keys(root, ["foo", "noexist"]))
print(find_value_by_keys(root, ["foo", 2]))

That will give you:

baz
baz
None
None

while failing fast and without resorting to a casting your tree to a default dict.

If you wanted to support a list of keys that was a dot separated sting you might be able to do that if you guarded for only passing integer indexes to lists. Perhaps something like:

def find_value_by_keys(root, list_of_keys):
    if isinstance(list_of_keys, str):
        list_of_keys = list_of_keys.split(".")

    for key in list_of_keys:
        try:
            root = root[int(key) if isinstance(root, list) else key]
        except (ValueError, KeyError):
            return None
    return root

root = {
    "foo": {"bar": "baz"},
    "foo2": ["bar", "baz"]
}
print(find_value_by_keys(root, ["foo", "bar"]))
print(find_value_by_keys(root, "foo.bar"))

print(find_value_by_keys(root, ["foo2", 1]))
print(find_value_by_keys(root, "foo2.1"))
print(find_value_by_keys(root, "foo2.x"))

print(find_value_by_keys(root, ["foo", "noexist"]))
print(find_value_by_keys(root, "foo.noexist"))

print(find_value_by_keys(root, ["foo", 2]))
print(find_value_by_keys(root, "foo.2"))
0
user2390182 On

The elegant random depth way to do this:

tree = lambda: defaultdict(tree)

base = tree()
base["noexist"]["nothereeither"]["nope"]

Now, this returns an empty defaultdict which you would have to handle, e.g.:

print(base["noexist"]["nothereeither"]["nope"] or None)

The less pretty, but more to the point, special variant for exactly 3 nesting levels:

deep3 = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: None)))
print(deep3["noexist"]["nothereeither"]["nope"])
# None

That being said the cleanest would be to access your dict with a special function:

def access(obj, keys, default=None):
    if not keys:
        return obj
    head, *tail = keys
    if head not in obj:
        return default
    return access(obj[head], tail, default=default)

print(access(base, ["foo23", "subfoo", "subsubfoo"]))
# None