Reputation: 91959
my dict looks like
d = {
'name': 'name',
'date': 'date',
'amount': 'amount',
...
}
I want to test if name
and amount
exists, so I will do
if not `name` in d and not `amount` in d:
raise ValueError # for example
Suppose I get the data from an api and I want to test if 10
of the fields exists in the dictionary or not.
Is it still the best way to look for?
Upvotes: 20
Views: 23370
Reputation: 1630
if you want to check if name
and amount
are in the dictionary you have to use a different condition, for example
if not ('name' in d and 'amount' in d):
raise ValueError # for example
that is checking that all keys are present. Using only the intersection you get the common keys, to know if all are present in the dictionary the lenght of the intersection has to be checked as well.
This is done in the function 'dictHasAllKeys' of this sample (Python 3) code
def dictCommonKeys (dicobj, keylist):
return keylist & dicobj.keys()
def dictHasAllKeys (dicobj, keylist):
return len (keylist & dicobj.keys()) == len(keylist)
keys = [ "name", "amount" ]
d1 = { "name": 1, "date": 2, "amount": 4 }
d2 = { "name": 1, "date": 2 }
print ("dictCommonKeys d1 " + str(dictCommonKeys(d1, keys)))
print ("dictCommonKeys d2 " + str(dictCommonKeys(d2, keys)))
print ("dictHasAllKeys d1 " + str(dictHasAllKeys(d1, keys)))
print ("dictHasAllKeys d2 " + str(dictHasAllKeys(d2, keys)))
that outputs
dictCommonKeys d1 {'amount', 'name'}
dictCommonKeys d2 {'name'}
dictHasAllKeys d1 True
dictHasAllKeys d2 False
Upvotes: 0
Reputation: 155363
For maximum efficiency, you want to avoid constructing unnecessary temporary set
s (which all the non-comparison binary operators require). You can do this for:
if name not in d and amount not in d:
with:
if d.keys().isdisjoint(("amount", "name")):
but that's likely the wrong logic (in both cases), since it only enters the if
body (and raises the exception) when both keys are missing, and you likely want to raise the exception if either key is missing.
For the more likely intended logic of rejecting d
unless it contains both keys, you'd want this:
if name not in d or amount not in d:
which can be done with set operations this way:
First, you'd predefine (outside the function to avoid repeated construction of a set):
required_keys = frozenset({"amount", "name"})
then do:
if not d.keys() >= required_keys:
Both solutions (isdisjoint
and >=
) avoid constructing per test temporaries sets, and should operate as efficiently as possible (short-circuiting as soon as a single key is found to be missing, and only needing a pair of O(1)
lookups when both keys are present). Personally, for just two keys, I'd stick with if name not in d or amount not in d:
, but if the number of keys grows, sure, use set-like operations.
Upvotes: 0
Reputation: 1121584
You can use set
intersections:
if not d.viewkeys() & {'amount', 'name'}:
raise ValueError
In Python 3, that'd be:
if not d.keys() & {'amount', 'name'}:
raise ValueError
because .keys()
returns a dict view by default. Dictionary view objects such as returned by .viewkeys()
(and .keys()
in Python 3) act as sets and intersection testing is very efficient.
Demo in Python 2.7:
>>> d = {
... 'name': 'name',
... 'date': 'date',
... 'amount': 'amount',
... }
>>> not d.viewkeys() & {'amount', 'name'}
False
>>> del d['name']
>>> not d.viewkeys() & {'amount', 'name'}
False
>>> del d['amount']
>>> not d.viewkeys() & {'amount', 'name'}
True
Note that this tests True only if both keys are missing. If you need your test to pass if either is missing, use:
if not d.viewkeys() >= {'amount', 'name'}:
raise ValueError
which is False only if both keys are present:
>>> d = {
... 'name': 'name',
... 'date': 'date',
... 'amount': 'amount',
... }
>>> not d.viewkeys() >= {'amount', 'name'}
False
>>> del d['amount']
>>> not d.viewkeys() >= {'amount', 'name'})
True
For a strict comparison (allowing only the two keys, no more, no less), in Python 2, compare the dictionary view against a set:
if d.viewkeys() != {'amount', 'name'}:
raise ValueError
(So in Python 3 that would be if d.keys() != {'amount', 'name'}
).
Upvotes: 50
Reputation: 103774
I like this form:
>>> d = {
... 'name': 'name',
... 'date': 'date',
... 'amount': 'amount'
... }
>>> tests={'name','date'}
>>> if any(test not in d for test in tests):
... raise ValueError
>>> # no error...
>>> del d['name']
>>> if any(test not in d for test in tests):
... raise ValueError
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError
Works on Py 2 or Py 3
Upvotes: 0
Reputation: 133534
if all(k not in d for k in ('name', 'amount')):
raise ValueError
or
if all(k in d for k in ('name', 'amount')):
# do stuff
Upvotes: 11
Reputation: 3555
You also could use set as:
>>> d = {
'name': 'name',
'date': 'date',
'amount': 'amount',
}
>>> test = set(['name','date'])
>>> test.issubset(set(d.keys()))
True
Upvotes: 5