tavor999
tavor999

Reputation: 477

python check existence of multiple multi level dict keys

How could this code be improved, or a method created, to checked for 1..N multi level dict key combinations? I am unclear on a clean way to pass that information into a method if using that approach.

   try:
        foo = event['key1']['key2']['key3']
    except KeyError:
        try:
            foo = event['Key1']['Key2']
        except KeyError as e::
            print(f"Unable to get foo from event: {e}")
            foo = "unknown"

Upvotes: 0

Views: 608

Answers (4)

Boskosnitch
Boskosnitch

Reputation: 774

This is the perfect situation for a recursive function. The following solution assumes that the first item in the list passed will be found in the highest level of the nested dict, but that can be accounted for pretty easily if I had more time.

def check_key_combos(d, keys: list):
    if len(keys)==0:
        return d
    for key, val in d.items():
        if key == keys[0]:
            keys.pop(0)
            return check_key_combos(d[key],keys)

Example: Dictionary 'd' is nested 4 layers with the final layer comprising of an array as a value.

>>>d
A
    1
        A
            1
                [8 9 8]
            2
                [7 8 5]
            3
                [6 3 9]
            4
                [3 3 4]
        B
            1
                [8 9 8]
            2
                [7 8 5]
            3
                [6 3 9]
            4
                [3 3 4]
        C
            1
                [8 9 8]
            2
                [7 8 5]
            3
                [6 3 9]
            4
                [3 3 4]
    2
        A
            1
                [8 9 8]
            2
                [7 8 5]
            3
                [6 3 9]
            4
                [3 3 4]
        B
            1
                [8 9 8]
            2
                [7 8 5]
            3
                [6 3 9]
            4
                [3 3 4]
        C
            1
                [8 9 8]
            2
                [7 8 5]
            3
                [6 3 9]
            4
                [3 3 4]
>>> nested_val = check_key_combos(d,['A','2','B','4'])
>>> nested_val
array([3, 3, 4])

Upvotes: 1

Max Shouman
Max Shouman

Reputation: 1331

This looks like the perfect situation for a recursive function.

The following function takes as arguments a dictionary (event), an ordered list of keys and a counter variable n incremented at each recursion.

The function looks for the n-th element of keys in the event dictionary; if it finds the key, the respective value is assigned to the event variable, the counter is incremented and the function is called recursively with the new parameters.

If the key is not found, or the n-th element is out of keys' range, or if event is not a dict type anymore, the program stops and prints out the event variable itself, which is indeed the value of the key used in the previous recursion.

Also if the very first key is not found in the dictionary, an error is shown.

def find_key(event, keys, n=0):
    try:
        event = event[keys[n]]
        n += 1
        find_key(event, keys, n=n)
    except:
        print(event if n != 0 else "The first key you provided was not found in the dictionary.")

As you can see, the code's logic is pretty simple and straightforward, so for instance:

event = {"key1":{"key2":{"key3":{"key4": "Hello World!"}}}}
keys = ["key1", "key2", "key3", "key4"]

find_key(event, keys)

would print out:

>>>Hello World!
    

Upvotes: 2

tavor999
tavor999

Reputation: 477

@crcvd had a very good answer but I had to modify it to suite my needs to maximize functionality in the method and reduce code on callers. This does assume that keys are somewhat unique so last key, couldn't match an earlier position.

def probe(dictionary, list_of_lists, default):
    for path in list_of_lists:
        for key in path:
            try:
                value = value[key]
            except UnboundLocalError:
                # value needs to be instantiated
                value = dictionary[key]
            except (KeyError, TypeError):
                print(f"The path {path} at key {key} is not reachable in {dictionary}")
                break
            else:
                if path[-1] == key:
                    return value

    return default


target = {
    "k1": {
        "k2": {
            "k3": "k3 final value"
        }
    }
}

l1 = ["k1", "k2", "k3"]
l2 = ["k1", "k2", "k5"]

v = probe(target, [l1, l2], "default value")
print(f" value = {v}")

Upvotes: 0

crcvd
crcvd

Reputation: 1535

You want to be able to specify some arbitrary-length path and move through that path. If you're able to reach the end of the path, return that value; otherwise, indicate the path cannot be reached.

def probe(dictionary, path):
    first = True

    # Treat the empty path [] as a reference to .
    if not path:
        return dictionary

    for key in path:
        try:
            if first:
                value = dictionary[key]
                first = False
            else:
                value = value[key]
        # KeyError: dictionary has key accessor but not this
        # specific key.
        # TypeError: The object is either not subscriptable or
        # the key is not hashable.
        except (KeyError, TypeError):
            raise ValueError(f"The path {path} is not reachable in {dictionary}")

    return value

You can then call this like so:

probe(some_dictionary, ["a", "b", "c"])

You could modify the signature like this (replacing references to path accordingly):

def probe(dictionary, *args):

and use a simpler syntax on calls:

probe(some_dictionary, "a", "b", "c")

Upvotes: 1

Related Questions