Reputation: 477
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
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
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
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
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