Is there are more readable way to check if a key buried in a dict exists without checking each level independently?
Lets say I need to get this value in a object buried (example taken from Wikidata):
x = s['mainsnak']['datavalue']['value']['numeric-id']
To make sure that this does not end with a runtime error it is necessary to either check every level like so:
if 'mainsnak' in s and 'datavalue' in s['mainsnak'] and 'value' in s['mainsnak']['datavalue'] and 'nurmeric-id' in s['mainsnak']['datavalue']['value']: x = s['mainsnak']['datavalue']['value']['numeric-id']
The other way I can think of to solve this is wrap this into a try catch
construct which I feel is also rather awkward for such a simple task.
I am looking for something like:
x = exists(s['mainsnak']['datavalue']['value']['numeric-id'])
which returns True
if all levels exists.
Advertisement
Answer
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)