Test if dictionary key exists, is not None and isn't blank - python

I have code that works but I'm wondering if there is a more pythonic way to do this. I have a dictionary and I want to see if:
a key exists
that value isn't None (NULL from SQL in this case)
that value isn't simply quote quote (blank?)
that value doesn't solely consist of spaces
So in my code the keys of "a", "b", and "c" would succeed, which is correct.
import re
mydict = {
"a":"alpha",
"b":0,
"c":False,
"d":None,
"e":"",
"g":" ",
}
#a,b,c should succeed
for k in mydict.keys():
if k in mydict and mydict[k] is not None and not re.search("^\s*$", str(mydict[k])):
print(k)
else:
print("I am incomplete and sad")
What I have above works, but that seems like an awfully long set of conditions. Maybe this simply is the right solution but I'm wondering if there is a more pythonic "exists and has stuff" or better way to do this?
UPDATE
Thank you all for wonderful answers and thoughtful comments. With some of the points and tips, I've updated the question a little bit as there some conditions I didn't have which should also succeed. I have also changed the example to a loop (just easier to test right?).

Try to fetch the value and store it in a variable, then use object "truthyness" to go further on with the value
v = mydict.get("a")
if v and v.strip():
if "a" is not in the dict, get returns None and fails the first condition
if "a" is in the dict but yields None or empty string, test fails, if "a" yields a blank string, strip() returns falsy string and it fails too.
let's test this:
for k in "abcde":
v = mydict.get(k)
if v and v.strip():
print(k,"I am here and have stuff")
else:
print(k,"I am incomplete and sad")
results:
a I am here and have stuff
b I am incomplete and sad # key isn't in dict
c I am incomplete and sad # c is None
d I am incomplete and sad # d is empty string
e I am incomplete and sad # e is only blanks
if your values can contain False, 0 or other "falsy" non-strings, you'll have to test for string, in that case replace:
if v and v.strip():
by
if v is not None and (not isinstance(v,str) or v.strip()):
so condition matches if not None and either not a string (everything matches) or if a string, the string isn't blank.

The get method for checking if a key exists is more efficient that iterating through the keys. It checks to see if the key exists without iteration using an O(1) complexity as apposed to O(n). My preferred method would look something like this:
if mydict.get("a") is not None and str(mydict.get("a")).replace(" ", "") != '':
# Do some work

You can use a list comprehension with str.strip to account for whitespace in strings.
Using if v is natural in Python to cover False-like objects, e.g. None, False, 0, etc. So note this only works if 0 is not an acceptable value.
res = [k for k, v in mydict.items() if (v.strip() if isinstance(v, str) else v)]
['a']

Here's a simple one-liner to check:
The key exists
The key is not None
The key is not ""
bool(myDict.get("some_key"))
As for checking if the value contains only spaces, you would need to be more careful as None doesn't have a strip() method.
Something like this as an example:
try:
exists = bool(myDict.get('some_key').strip())
except AttributeError:
exists = False

Well I have 2 suggestions to offer you, especially if your main issue is the length of the conditions.
The first one is for the check if the key is in the dict. You don't need to use "a" in mydict.keys() you can just use "a" in mydict.
The second suggestion to make the condition smaller is to break down into smaller conditions stored as booleans, and check these in your final condition:
import re
mydict = {
"a":"alpha",
"c":None,
"d":"",
"e":" ",
}
inKeys = True if "a" in mydict else False
isNotNone = True if mydict["a"] is not None else False
isValidKey = True if not re.search("^\s*$", mydict["a"]) else False
if inKeys and isNotNone and isValidKey:
print("I am here and have stuff")
else:
print("I am incomplete and sad")

it check exactly for NoneType not only None
from types import NoneType # dont forget to import this
mydict = {
"a":"alpha",
"b":0,
"c":False,
"d":None,
"e":"",
"g":" ",
}
#a,b,c should succeed
for k in mydict:
if type(mydict[k]) != NoneType:
if type(mydict[k]) != str or type(mydict[k]) == str and mydict[k].strip():
print(k)
else:
print("I am incomplete and sad")
else:
print("I am incomplete and sad")

cond is a generator function responsible for generating conditions to apply in a short-circuiting manner using the all function. Given d = cond(), next(d) will check if a exists in the dict, and so on until there is no condition to apply, in that case all(d) will evaluate to True.
mydict = {
"a":"alpha",
"c":None,
"d":"",
"e":" ",
}
def cond ():
yield 'a' in mydict
yield mydict ['a']
yield mydict ['a'].strip ()
if all (cond ()):
print("I am here and have stuff")
else:
print("I am incomplete and sad")

Related

How to check that only one value of my dictionary is filled?

How can I check that my dict contains only one value filled ?
I want to enter in my condition only if the value is the only one in my dict and of this type (in my example "test2") of my dict.
For now I have this if statement
my_dict = {}
my_dict["test1"] = ""
my_dict["test2"] = "example"
my_dict["test3"] = ""
my_dict["test4"] = ""
if my_dict["test2"] and not my_dict["test1"] and not my_dict["test3"] and not my_dict["test4"]:
print("inside")
I would like to find a better, classy and "pep8" way to achieve that
Any ideas ?
You have to check every value for truthiness, there's no way around that, e.g.
if sum(1 for v in my_dict.values() if v) == 1:
print('inside')
You can use filter() as below to check how many values are there in the dictionary.
if len(list(filter(None, my_dict.values()))) == 1:
print("inside")
Assuming that all your values are strings, what about
ref_key = "test2"
if ''.join(my_dict.values()) == my_dict[ref_key]:
print("inside")
... since it looks like you have a precise key in mind (when you do if my_dict["test2"]). Otherwise, my answer is (twice) less general than (some) others'.
Maybe you want to check if there's only one pair in dictionary after removing the empty values.
my_dict = {}
my_dict["test1"] = ""
my_dict["test2"] = "example"
my_dict["test3"] = ""
my_dict["test4"] = ""
my_dict={key:val for key,val in my_dict.items() if val}
if len(my_dict)==1:
print("inside")
Here is the another flavour (without loops):
data = list(my_dict.values())
if data.count('') + 1 == len(data):
print("Inside")

Simplifying many if-statements

Is there a way to simplify this pile of if-statements? This parsing function sure works (with the right dictionaries), but it has to test 6 if-statements for each word in the input. For a 5-word sentence that would be 30 if-statements. It is also kind of hard to read.
def parse(text):
predicate=False
directObjectAdjective=False
directObject=False
preposition=False
indirectObjectAdjective=False
indirectObject=False
text=text.casefold()
text=text.split()
for word in text:
if not predicate:
if word in predicateDict:
predicate=predicateDict[word]
continue
if not directObjectAdjective:
if word in adjectiveDict:
directObjectAdjective=adjectiveDict[word]
continue
if not directObject:
if word in objectDict:
directObject=objectDict[word]
continue
if not preposition:
if word in prepositionDict:
preposition=prepositionDict[word]
continue
if not indirectObjectAdjective:
if word in adjectiveDict:
indirectObjectAdjective=adjectiveDict[word]
continue
if not indirectObject:
if word in objectDict:
indirectObject=objectDict[word]
continue
if not directObject and directObjectAdjective:
directObject=directObjectAdjective
directObjectAdjective=False
if not indirectObject and indirectObjectAdjective:
indirectObject=indirectObjectAdjective
indirectObjectAdjective=False
return [predicate,directObjectAdjective,directObject,preposition,indirectObjectAdjective,indirectObject]
Here's also a sample of a dictionary, if that's needed.
predicateDict={
"grab":"take",
"pick":"take",
"collect":"take",
"acquire":"take",
"snag":"take",
"gather":"take",
"attain":"take",
"capture":"take",
"take":"take"}
This is more of a Code Review question than a Stack Overflow one. A major issue is that you have similar data that you're keeping in separate variables. If you combine your variables, then you can iterate over them.
missing_parts_of_speech = ["predicate", [...]]
dict_look_up = {"predicate":predicateDict,
[...]
}
found_parts_of_speech = {}
for word in text:
for part in missing_parts_of_speech:
if word in dict_look_up[part]:
found_parts_of_speech[part] = dict_look_up[part][word]
missing_parts_of_speech.remove(part)
continue
I would suggest to simply use the method dict.get. This method has the optional argument default. By passing this argument you can avoid a KeyError. If the key is not present in a dictionary, the default value will be returned.
If you use the previously assigned variable as default, it will not be replaced by an arbitrary value, but the correct value. E.g., if the current word is a "predicate" the "direct object" will be replaced by the value that was already stored in the variable.
CODE
def parse(text):
predicate = False
directObjectAdjective = False
directObject = False
preposition = False
indirectObjectAdjective = False
indirectObject = False
text=text.casefold()
text=text.split()
for word in text:
predicate = predicateDict.get(word, predicate)
directObjectAdjective = adjectiveDict.get(word, directObjectAdjective)
directObject = objectDict.get(word, directObject)
preposition = prepositionDict.get(word, preposition)
indirectObjectAdjective = adjectiveDict.get(word, indirectObjectAdjective)
indirectObject = objectDict.get(word, indirectObject)
if not directObject and directObjectAdjective:
directObject = directObjectAdjective
directObjectAdjective = False
if not indirectObject and indirectObjectAdjective:
indirectObject = indirectObjectAdjective
indirectObjectAdjective = False
return [predicate, directObjectAdjective, directObject, preposition, indirectObjectAdjective, indirectObject]
PS: Use a little more spaces. Readers will thank you...
PPS: I have not tested this, for I do not have such dictionaries at hand.
PPPS: This will always return the last occurances of the types within the text, while your implementation will always return the first occurances.
You could map the different kinds of words (as strings) to dictionaries where to find those words, and then just check which of those have not been found yet and look them up if they are in those dicts.
needed = {"predicate": predicateDict,
"directObjectAdjective": adjectiveDict,
"directObject": objectDict,
"preposition": prepositionDict,
"indirectObjectAdjective": adjectiveDict,
"indirectObject": objectDict}
for word in text:
for kind in needed:
if isinstance(needed[kind], dict) and word in needed[kind]:
needed[kind] = needed[kind][word]
continue
In the end (and in each step on the way) all the items in needed that do not have a dict as a value have been found and replaced by the value from their respective dict.
(In retrospect, it might make more sense to ue two dictionaries, or one dict and a set: One for the final value for that kind of word, and one for whether they have already been found. Would probably be a bit easier to grasp.)
I suggest that you use a new pattern to write this code instead the old one. The new pattern has 9 lines and stay 9 lines - just add more dictionaries to D. The old has already 11 lines and will grow 4 lines with every additional dictionaries to test.
aDict = { "a1" : "aa1", "a2" : "aa1" }
bDict = { "b1" : "bb1", "b2" : "bb2" }
text = ["a1", "b2", "a2", "b1"]
# old pattern
a = False
b = False
for word in text:
if not a:
if word in aDict:
a = aDict[word]
continue
if not b:
if word in bDict:
b = bDict[word]
continue
print(a, b)
# new pattern
D = [ aDict, bDict]
A = [ False for _ in D]
for word in text:
for i, a in enumerate(A):
if not a:
if word in D[i]:
A[i] = D[i][word]
continue
print(A)

Best way to write for loop while being able to access items in container Python

This is relatively straightforward but more pointed question on how to write the most concise if/any/for loop that accomplishes what I need. I'm rather new to Python, and much more used to the C++ way of doing things.
I need to do something like the following:
for item in my_dict:
if some_var == item.some_attribute:
if item.another_attribute exists:
variable = "A"
else variable = "B"
print "Duplicate item exists in my list of type: {}".format(variable)
I've condensed it into something like this:
if any(some_var == item.some_attribute for item in my_dict):
variable = "A" if item.another_attribute else "B"
print "Duplicate item exists in my list of type: {}".format(variable)
However, when doing this, I'm getting an "unresolved reference" error for item. Any ideas as to how I can write a concise loop, equivalence check, and presence check like I've described above in a way that allows me to access and perform methods on item?
Thank you!
EDIT: thank you so much for the answer #pjhaugh, that's exactly what I was looking for. Works beautifully! Thank you everyone for your helpful comments as well.
What's happening here is that item does not leak out of the generator expression scope (this is a good thing). You could obtain item by getting the next thing yielded by a generator, or catching that generator yielding nothing.
try:
item = next(item for item in my_dict if some_var == item.some_attribute)
variable = "A" if item.another_attribute else "B"
except StopIteration:
# there is no such item in my_dict
You can extract the objects yielded by an iterator in a for loop like this
for item in (i for i in my_dict if some_var == item.some_attribute):
variable = "A" if item.another_attribute is not None else "B"
print("Duplicate item exists in my list of type: {}".format(variable))
break
else: # no break encountered
... # not found
Currently you're iterating over the keys of the dictionary. If you want the items (pairs in c++ lingo), you need my_dict.items().
As pointed out in my comment - the any(some_var == item.some_attribute for item in my_dict) builds its own local scope. You cannot use item outside of it.
You can however loop over your dict like so:
Create minimal verifyable complete example data:
class K:
def __init__(self):
self.some_attribute = None
self.another_attribute = None
def __repr__(self):
return "{} - {}".format(self.some_attribute,self.another_attribute)
def __str__(self):
return "{} - {}".format(self.some_attribute,self.another_attribute)
k1 = K()
k1.some_attribute = 99
k2 = K()
k2.some_attribute = 33
k3 = K()
k3.some_attribute = 33
k3.another_attribute = 1
my_dict = { 1: k1, 2:k2, 3:k3}
some_var = 33
You can use a for loop like so:
variable = None
for item in my_dict.items():
k,v = item
if v.some_attribute != some_var:
print "No dupe: {}".format(v)
continue
if v.some_attribute == some_var and v.another_attribute is not None:
variable = "A"
else:
variable = "B"
print "Duplicate item exists in my list of type: {}".format(variable)
print v
Output:
No dupe: 99 - None
Duplicate item exists in my list of type: B
33 - None
Duplicate item exists in my list of type: A
33 - 1

Python : check if the nested dictionary exist

I have several nested dictionaries within lists, and I need to verify if a specific path exist e.g.
dict1['layer1']['layer2'][0]['layer3']
How can I check with an IF statement if the path is valid?
I was thinking to
if dict1['layer1']['layer2'][0]['layer3'] :
but it doesn't work
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
I wanted to propose another solution, because I've been thinking about it too.
if not dict1.get("layer1", {}).get("layer2", {})[0].get("layer3", {}):
...
dict.get() attempts to get the key at each stage.
If the key is not present, an empty dict will be returned, instead of the nested dict (this is needed, because trying to call .get() on the default return of None will yield an AttributeError).
If the return is empty, it will evaluate to false.
So, this wouldn't work if the final result was an empty dict anyway, but if you can guarantee the results will be filled, this is an alternative that is fairly simple.
As far as I know, you've to go step by step, i.e.:
if 'layer1' in dict1:
if 'layer2' in dict1['layer1']
ans so on...
If you don't want to go the try/except route, you could whip up a quick method to do this:
def check_dict_path(d, *indices):
sentinel = object()
for index in indices:
d = d.get(index, sentinel)
if d is sentinel:
return False
return True
test_dict = {1: {'blah': {'blob': 4}}}
print check_dict_path(test_dict, 1, 'blah', 'blob') # True
print check_dict_path(test_dict, 1, 'blah', 'rob') # False
This might be redundant if you're also trying to retrieve the object at that location (rather than just verify whether the location exists). If that's the case, the above method can easily be updated accordingly.
Here is a similar question with the answer I would recommend:
Elegant way to check if a nested key exists in a python dict
Using recursive function:
def path_exists(path, dict_obj, index = 0):
if (type(dict_obj) is dict and path[index] in dict_obj.keys()):
if (len(path) > (index+1)):
return path_exists(path, dict_obj[path[index]], index + 1)
else:
return True
else:
return False
Where path is a list of strings representing the nested keys.

Elegant way to check if a nested key exists in a dict?

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.
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)
You could use .get with defaults:
s.get('mainsnak', {}).get('datavalue', {}).get('value', {}).get('numeric-id')
but this is almost certainly less clear than using try/except.
Python 3.8 +
dictionary = {
"main_key": {
"sub_key": "value",
},
}
if sub_key_value := dictionary.get("main_key", {}).get("sub_key"):
print(f"The key 'sub_key' exists in dictionary[main_key] and it's value is {sub_key_value}")
else:
print("Key 'sub_key' doesn't exists or their value is Falsy")
Extra
A little but important clarification.
In the previous code block, we verify that a key exists in a dictionary but that its value is also Truthy.
Most of the time, this is what people are really looking for, and I think this is what the OP really wants. However, it is not really the most "correct" answer, since if the key exists but its value is False, the above code block will tell us that the key does not exist, which is not true.
So, I leet here a more correct answer:
dictionary = {
"main_key": {
"sub_key": False,
},
}
if "sub_key" in dictionary.get("main_key", {}):
print(f"The key 'sub_key' exists in dictionary[main_key] and it's value is {dictionary['main_key']['sub_key']}")
else:
print("Key 'sub_key' doesn't exists")
Try/except seems to be most pythonic way to do that.
The following recursive function should work (returns None if one of the keys was not found in the dict):
def exists(obj, chain):
_key = chain.pop(0)
if _key in obj:
return exists(obj[_key], chain) if chain else obj[_key]
myDict ={
'mainsnak': {
'datavalue': {
'value': {
'numeric-id': 1
}
}
}
}
result = exists(myDict, ['mainsnak', 'datavalue', 'value', 'numeric-id'])
print(result)
>>> 1
I suggest you to use python-benedict, a solid python dict subclass with full keypath support and many utility methods.
You just need to cast your existing dict:
s = benedict(s)
Now your dict has full keypath support and you can check if the key exists in the pythonic way, using the in operator:
if 'mainsnak.datavalue.value.numeric-id' in s:
# do stuff
Here the library repository and the documentation:
https://github.com/fabiocaccamo/python-benedict
Note: I am the author of this project
You can use pydash to check if exists: http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has
Or get the value (you can even set default - to return if doesn't exist): http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has
Here is an example:
>>> get({'a': {'b': {'c': [1, 2, 3, 4]}}}, 'a.b.c[1]')
2
The try/except way is the most clean, no contest. However, it also counts as an exception in my IDE, which halts execution while debugging.
Furthermore, I do not like using exceptions as in-method control statements, which is essentially what is happening with the try/catch.
Here is a short solution which does not use recursion, and supports a default value:
def chained_dict_lookup(lookup_dict, keys, default=None):
_current_level = lookup_dict
for key in keys:
if key in _current_level:
_current_level = _current_level[key]
else:
return default
return _current_level
The accepted answer is a good one, but here is another approach. It's a little less typing and a little easier on the eyes (in my opinion) if you end up having to do this a lot. It also doesn't require any additional package dependencies like some of the other answers. Have not compared performance.
import functools
def haskey(d, path):
try:
functools.reduce(lambda x, y: x[y], path.split("."), d)
return True
except KeyError:
return False
# Throwing in this approach for nested get for the heck of it...
def getkey(d, path, *default):
try:
return functools.reduce(lambda x, y: x[y], path.split("."), d)
except KeyError:
if default:
return default[0]
raise
Usage:
data = {
"spam": {
"egg": {
"bacon": "Well..",
"sausages": "Spam egg sausages and spam",
"spam": "does not have much spam in it",
}
}
}
(Pdb) haskey(data, "spam")
True
(Pdb) haskey(data, "spamw")
False
(Pdb) haskey(data, "spam.egg")
True
(Pdb) haskey(data, "spam.egg3")
False
(Pdb) haskey(data, "spam.egg.bacon")
True
Original inspiration from the answers to this question.
EDIT: a comment pointed out that this only works with string keys. A more generic approach would be to accept an iterable path param:
def haskey(d, path):
try:
functools.reduce(lambda x, y: x[y], path, d)
return True
except KeyError:
return False
(Pdb) haskey(data, ["spam", "egg"])
True
I had the same problem and recent python lib popped up:
https://pypi.org/project/dictor/
https://github.com/perfecto25/dictor
So in your case:
from dictor import dictor
x = dictor(s, 'mainsnak.datavalue.value.numeric-id')
Personal note:
I don't like 'dictor' name, since it doesn't hint what it actually does. So I'm using it like:
from dictor import dictor as extract
x = extract(s, 'mainsnak.datavalue.value.numeric-id')
Couldn't come up with better naming than extract. Feel free to comment, if you come up with more viable naming. safe_get, robust_get didn't felt right for my case.
Another way:
def does_nested_key_exists(dictionary, nested_key):
exists = nested_key in dictionary
if not exists:
for key, value in dictionary.items():
if isinstance(value, dict):
exists = exists or does_nested_key_exists(value, nested_key)
return exists
The selected answer works well on the happy path, but there are a couple obvious issues to me. If you were to search for ["spam", "egg", "bacon", "pizza"], it would throw a type error due to trying to index "well..." using the string "pizza". Like wise, if you replaced pizza with 2, it would use that to get the index 2 from "Well..."
Selected Answer Output Issues:
data = {
"spam": {
"egg": {
"bacon": "Well..",
"sausages": "Spam egg sausages and spam",
"spam": "does not have much spam in it"
}
}
}
print(keys_exists(data, "spam", "egg", "bacon", "pizza"))
>> TypeError: string indices must be integers
print(keys_exists(data, "spam", "egg", "bacon", 2)))
>> l
I also feel that using try except can be a crutch that we might too quickly rely on. Since I believe we already need to check for the type, might as well remove the try except.
Solution:
def dict_value_or_default(element, keys=[], default=Undefined):
'''
Check if keys (nested) exists in `element` (dict).
Returns value if last key exists, else returns default value
'''
if not isinstance(element, dict):
return default
_element = element
for key in keys:
# Necessary to ensure _element is not a different indexable type (list, string, etc).
# get() would have the same issue if that method name was implemented by a different object
if not isinstance(_element, dict) or key not in _element:
return default
_element = _element[key]
return _element
Output:
print(dict_value_or_default(data, ["spam", "egg", "bacon", "pizza"]))
>> INVALID
print(dict_value_or_default(data, ["spam", "egg", "bacon", 2]))
>> INVALID
print(dict_value_or_default(data, ["spam", "egg", "bacon"]))
>> "Well..."
Here's my small snippet based on #Aroust's answer:
def exist(obj, *keys: str) -> bool:
_obj = obj
try:
for key in keys:
_obj = _obj[key]
except (KeyError, TypeError):
return False
return True
if __name__ == '__main__':
obj = {"mainsnak": {"datavalue": {"value": "A"}}}
answer = exist(obj, "mainsnak", "datavalue", "value", "B")
print(answer)
I added TypeError because when _obj is str, int, None, or etc, it would raise that error.
I wrote a data parsing library called dataknead for cases like this, basically because i got frustrated by the JSON the Wikidata API returns as well.
With that library you could do something like this
from dataknead import Knead
numid = Knead(s).query("mainsnak/datavalue/value/numeric-id").data()
if numid:
# Do something with `numeric-id`
Using dict with defaults is concise and appears to execute faster than using consecutive if statements.
Try it yourself:
import timeit
timeit.timeit("'x' in {'a': {'x': {'y'}}}.get('a', {})")
# 0.2874350370002503
timeit.timeit("'a' in {'a': {'x': {'y'}}} and 'x' in {'a': {'x': {'y'}}}['a']")
# 0.3466246419993695
I have written a handy library for this purpose.
I am iterating over ast of the dict and trying to check if a particular key is present or not.
Do check this out.
https://github.com/Agent-Hellboy/trace-dkey
If you can suffer testing a string representation of the object path then this approach might work for you:
def exists(str):
try:
eval(str)
return True
except:
return False
exists("lst['sublist']['item']")
one can try to use this for checking whether key/nestedkey/value is in nested dict
import yaml
#d - nested dictionary
if something in yaml.dump(d, default_flow_style=False):
print(something, "is in", d)
else:
print(something, "is not in", d)
There are many great answers. here is my humble take on it. Added check for array of dictionaries as well. Please note that I am not checking for arguments validity. I used part Arnot's code above. I added this answer because a I got a use case that requires checking array or dictionaries in my data.
Here is the code:
def keys_exists(element, *keys):
'''
Check if *keys (nested) exists in `element` (dict).
'''
retval=False
if isinstance(element,dict):
for key,value in element.items():
for akey in keys:
if element.get(akey) is not None:
return True
if isinstance(value,dict) or isinstance(value,list):
retval= keys_exists(value, *keys)
elif isinstance(element, list):
for val in element:
if isinstance(val,dict) or isinstance(val,list):
retval=keys_exists(val, *keys)
return retval

Categories

Resources