Elegant way to check if a nested key exists in a dict?
To be brief, with Python you must trust it is easier to ask for forgiveness than permission
try:
x = s['mainsnak']['datavalue']['value']['numeric-id']
except KeyError:
pass
The answer
Here is how I deal with nested dict keys:
def keys_exists(element, *keys):
'''
Check if *keys (nested) exists in `element` (dict).
'''
if not isinstance(element, dict):
raise AttributeError('keys_exists() expects dict as first argument.')
if len(keys) == 0:
raise AttributeError('keys_exists() expects at least two arguments, one given.')
_element = element
for key in keys:
try:
_element = _element[key]
except KeyError:
return False
return True
Example:
data = {
"spam": {
"egg": {
"bacon": "Well..",
"sausages": "Spam egg sausages and spam",
"spam": "does not have much spam in it"
}
}
}
print 'spam (exists): {}'.format(keys_exists(data, "spam"))
print 'spam > bacon (do not exists): {}'.format(keys_exists(data, "spam", "bacon"))
print 'spam > egg (exists): {}'.format(keys_exists(data, "spam", "egg"))
print 'spam > egg > bacon (exists): {}'.format(keys_exists(data, "spam", "egg", "bacon"))
Output:
spam (exists): True
spam > bacon (do not exists): False
spam > egg (exists): True
spam > egg > bacon (exists): True
It loop in given element
testing each key in given order.
I prefere this to all variable.get('key', {})
methods I found because it follows EAFP.
Function except to be called like: keys_exists(dict_element_to_test, 'key_level_0', 'key_level_1', 'key_level_n', ..)
. At least two arguments are required, the element and one key, but you can add how many keys you want.
If you need to use kind of map, you can do something like:
expected_keys = ['spam', 'egg', 'bacon']
keys_exists(data, *expected_keys)
Check if a key exists in a nested dictionary python based on a list
You can use functools.reduce()
for this, you just need to anticipate the KeyError
. For example:
from functools import reduce
d = {"key1" : {"key2": {"key3": 4 } } }
def findindict(d, l):
try:
return reduce(lambda current_dict, key: current_dict[key], l, d)
except (KeyError, TypeError):
return None
findindict(d, ["key1","key2", "key3"])
# 4
findindict(d, ["key1","key2", "abc"])
# None
findindict(d, ["key1","key2", "key3", "key6"])
#None
Python : check if the nested dictionary exist
Here's the explicit short code with try/except
:
try:
dict1['layer1']['layer2'][0]['layer3']
except KeyError:
present = False
else:
present = True
if present:
...
To get the element:
try:
obj = dict1['layer1']['layer2'][0]['layer3']
except KeyError:
obj = None # or whatever
How to check if a key exists in an inner dictionary inside a dictionary in python?
IF you can't use an exception for some reason (eg. lambda func, list comprehension, generator expression etc)
value = a.get(b, {}).get(c, {}).get("z", None)
But normally you should prefer to use the exception handler
Check key existence in nested dictionaries
If you are working with JSON, you can write a simple class to use with the dict as it is imported.
Given the following bit of JSON:
>>> js='{"a": {"b": {"c": {"d": "e"}}}}'
It would usually be decoded into a Python dict if it is comprised of object pairs:
>>> import json
>>> json.loads(js)
{u'a': {u'b': {u'c': {u'd': u'e'}}}}
As a normal Python dict, it is subject to KeyError
with missing keys. You can use the __missing__
hook to override KeyErrors
and achieve your original structure:
class Mdict(dict):
def __missing__(self, key):
return False
Now test that:
>>> md=Mdict({'a':Mdict({'b':Mdict({'c':Mdict({'d':'e'})})})})
>>> if md['a']['b']['d']:
... print md['a']['b']['d']
... elif md['a']['b']['c']:
... print 'elif', md['a']['b']['c']
...
elif {'d': 'e'}
Each level of the dict needs to be an Mdict
vs a normal Python dict
. If you are working with JSON, however, this is really easy to achieve. Just apply object_pairs_hook as you decode the JSON:
>>> js
'{"a": {"b": {"c": {"d": "e"}}}}'
>>> md=json.loads(js, object_pairs_hook=Mdict)
And that applies the class Mdict
instead the default Python dict
as the JSON is decoded.
>>> md
{u'a': {u'b': {u'c': {u'd': u'e'}}}}
>>> md['a']
{u'b': {u'c': {u'd': u'e'}}}
>>> md['a']['c']
False
The rest of the example here remains the same.
How to know if a value exists in a list within a nested dictionary
You can use any
with a list comprehension to check if 'Anna'
exists as either a key in the dictionary or appears as an element in a subdictionary:
data = {"Pam": {"Job":"Pilot",
"YOB": "1986",
"Children": ["Greg", "Anna","Sima"]},
"Joe": {"Job":"Engineer",
"YOB": "1987"}}
if any(a == 'Anna' or 'Anna' in b.get('children', []) for a, b in data.items()):
pass
Find all occurrences of a key in nested dictionaries and lists
I found this Q/A very interesting, since it provides several different solutions for the same problem. I took all these functions and tested them with a complex dictionary object. I had to take two functions out of the test, because they had to many fail results and they did not support returning lists or dicts as values, which i find essential, since a function should be prepared for almost any data to come.
So i pumped the other functions in 100.000 iterations through the timeit
module and output came to following result:
0.11 usec/pass on gen_dict_extract(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6.03 usec/pass on find_all_items(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.15 usec/pass on findkeys(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1.79 usec/pass on get_recursively(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.14 usec/pass on find(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.36 usec/pass on dict_extract(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
All functions had the same needle to search for ('logging') and the same dictionary object, which is constructed like this:
o = { 'temparature': '50',
'logging': {
'handlers': {
'console': {
'formatter': 'simple',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout',
'level': 'DEBUG'
}
},
'loggers': {
'simpleExample': {
'handlers': ['console'],
'propagate': 'no',
'level': 'INFO'
},
'root': {
'handlers': ['console'],
'level': 'DEBUG'
}
},
'version': '1',
'formatters': {
'simple': {
'datefmt': "'%Y-%m-%d %H:%M:%S'",
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
}
}
},
'treatment': {'second': 5, 'last': 4, 'first': 4},
'treatment_plan': [[4, 5, 4], [4, 5, 4], [5, 5, 5]]
}
All functions delivered the same result, but the time differences are dramatic! The function gen_dict_extract(k,o)
is my function adapted from the functions here, actually it is pretty much like the find
function from Alfe, with the main difference, that i am checking if the given object has iteritems function, in case strings are passed during recursion:
# python 2
def gen_dict_extract(key, var):
if hasattr(var,'iteritems'): # hasattr(var,'items') for python 3
for k, v in var.iteritems(): # var.items() for python 3
if k == key:
yield v
if isinstance(v, dict):
for result in gen_dict_extract(key, v):
yield result
elif isinstance(v, list):
for d in v:
for result in gen_dict_extract(key, d):
yield result
So this variant is the fastest and safest of the functions here. And find_all_items
is incredibly slow and far off the second slowest get_recursivley
while the rest, except dict_extract
, is close to each other. The functions fun
and keyHole
only work if you are looking for strings.
Interesting learning aspect here :)
Safe method to get value of nested dictionary
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')
Check or set entries in nested dict from list of keys of variable length
my_dict = {}
path = "a/e/f/g"
wd = my_dict # Set working dictionary to my_dict
path_split = path.split('/')
for char in path_split[:-1]: # Loop through all but the final char in the path
if char not in wd or not isinstance(wd[char], dict):
wd[char] = {}
wd = wd[char] # Set new working dictionary
if path_split[-1] not in wd: # Check if final char is in the wd, if not set it to 1
wd[path_split[-1]] = 1
You need to check that a key exists in the working dictionary and that the corresponding value is a dictionary, then set the working dictionary after each check.
Related Topics
How to Implement the Softmax Function in Python
How to Write a File or Data to an S3 Object Using Boto3
Django Rest Framework Upload Image: "The Submitted Data Was Not a File"
Split a List into Parts Based on a Set of Indexes in Python
Dll Load Failed When Importing Pyqt5
Subprocess.Call() Arguments Ignored When Using Shell=True W/ List
How to Insert Newlines on Argparse Help Text
Basic Python Client Socket Example
Where Is Python's "Best Ascii for This Unicode" Database
Cannot Pass an Argument to Python with "#!/Usr/Bin/Env Python"
Ssl Insecureplatform Error When Using Requests Package
Find Longest Repetitive Sequence in a String
Different Ways of Clearing Lists
Compiling with Cython and Mingw Produces Gcc: Error: Unrecognized Command Line Option '-Mno-Cygwin'