daydreamer
daydreamer

Reputation: 91959

python: what is best way to check multiple keys exists in a dictionary?

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

Answers (6)

Alejadro Xalabarder
Alejadro Xalabarder

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

ShadowRanger
ShadowRanger

Reputation: 155363

For maximum efficiency, you want to avoid constructing unnecessary temporary sets (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

Martijn Pieters
Martijn Pieters

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

dawg
dawg

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

jamylak
jamylak

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

Sheng
Sheng

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

Related Questions