44

Testing for equality works fine like this for python dicts:

first  = {"one":"un", "two":"deux", "three":"trois"}
second = {"one":"un", "two":"deux", "three":"trois"}

print(first == second) # Result: True

But now my second dict contains some additional keys I want to ignore:

first  = {"one":"un", "two":"deux", "three":"trois"}
second = {"one":"un", "two":"deux", "three":"trois", "foo":"bar"}

Is there a simple way to test if the first dict is part of the second dict, with all its keys and values?

EDIT 1:

This question is suspected to be a duplicate of How to test if a dictionary contains certain keys, but I'm interested in testing keys and their values. Just containing the same keys does not make two dicts equal.

EDIT 2:

OK, I got some answers now using four different methods, and proved all of them working. As I need a fast process, I tested each for execution time. I created three identical dicts with 1000 items, keys and values were random strings of length 10. The second and third got some extra key-value pairs, and the last non-extra key of the third got a new value. So, first is a subset of second, but not of third. Using module timeit with 10000 repetitions, I got:

Method                                                      Time [s]   
first.viewitems() <=second.viewitems()                           0.9 
set(first.items()).issubset(second.items())                      7.3
len(set(first.items()) & set(second.items())) == len(first)      8.5
all(first[key] == second.get(key, sentinel) for key in first)    6.0

I guessed the last method is the slowest, but it's on place 2. But method 1 beats them all.

Thanks for your answers!

Community
  • 1
  • 1
sweber
  • 2,916
  • 2
  • 15
  • 22
  • 2
    possible duplicate of [How to test if a dictionary contains certain keys](http://stackoverflow.com/questions/3415347/how-to-test-if-a-dictionary-contains-certain-keys) – tjati Jun 13 '15 at 12:38
  • 1
    Possible duplicate of [Python: Check if one dictionary is a subset of another larger dictionary](https://stackoverflow.com/questions/9323749/python-check-if-one-dictionary-is-a-subset-of-another-larger-dictionary) – Maor Refaeli Nov 15 '18 at 16:34

4 Answers4

87

You can use a dictionary view:

# Python 2
if first.viewitems() <= second.viewitems():
    # true only if `first` is a subset of `second`

# Python 3
if first.items() <= second.items():
    # true only if `first` is a subset of `second`

Dictionary views are the standard in Python 3, in Python 2 you need to prefix the standard methods with view. They act like sets, and <= tests if one of those is a subset of (or is equal to) another.

Demo in Python 3:

>>> first  = {"one":"un", "two":"deux", "three":"trois"}
>>> second = {"one":"un", "two":"deux", "three":"trois", "foo":"bar"}
>>> first.items() <= second.items()
True
>>> first['four'] =  'quatre'
>>> first.items() <= second.items()
False

This works for non-hashable values too, as the keys make the key-value pairs unique already. The documentation is a little confusing on this point, but even with mutable values (say, lists) this works:

>>> first_mutable = {'one': ['un', 'een', 'einz'], 'two': ['deux', 'twee', 'zwei']}
>>> second_mutable = {'one': ['un', 'een', 'einz'], 'two': ['deux', 'twee', 'zwei'], 'three': ['trois', 'drie', 'drei']}
>>> first_mutable.items() <= second_mutable.items()
True
>>> first_mutable['one'].append('ichi')
>>> first_mutable.items() <= second_mutable.items()
False

You could also use the all() function with a generator expression; use object() as a sentinel to detect missing values concisely:

sentinel = object()
if all(first[key] == second.get(key, sentinel) for key in first):
    # true only if `first` is a subset of `second`

but this isn't as readable and expressive as using dictionary views.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
7
all(k in second and second[k] == v for k, v in first.items())

if you know that none of the values can be None, it will simplify to:

all(second.get(k, None) == v for k, v in first.items())
behzad.nouri
  • 74,723
  • 18
  • 126
  • 124
  • Why did you remove the alternative version you posted earlier? `not (set(first.items()) - set(second.items()))` – Iskren Jun 13 '15 at 12:47
  • @Iskren because it will not work if values are not hashable, for example if `'foo':[1, 2, 3]` was one of the items. – behzad.nouri Jun 13 '15 at 12:50
  • 1
    Your second solution is very elegant, though it might get slow if values are deeply nested. Nevertheless it's highly polymorphic and concise. +1 – Eli Korvigo Jun 13 '15 at 13:08
4

So, you basically want to check if one dictionary is a subset of another.

first  = {"one":"un", "two":"deux", "three":"trois"}
second = {"one":"un", "two":"deux", "three":"trois", "foo":"bar"}

def subset_dic(subset, superset):
    return len(set(subset.items()) & set(superset.items())) == len(subset)


print(subset_dic(first, second))

Prints:

True

In case you want to abstract out the subset/superset part:

def subset_dic(dict1, dict2):
    return len(set(dict1.items()) & set(dict2.items())) == len(min((dict1, dict2), key=len))

Note: this will fail to work, if any value is a mutable object. Hence you can add an additional step (convert mutable object to immutable analog) in the function to overcome this limitation.

Eli Korvigo
  • 10,265
  • 6
  • 47
  • 73
  • Creating a set of both full dictionaries seems a bit expensive. – poke Jun 13 '15 at 12:54
  • Comparing two sets of tuples without hashing is even more expensive in terms of sheer complexity. – Eli Korvigo Jun 13 '15 at 12:56
  • Yes, but no need to compare tuples; dictionaries already have O(1) item access, so you can just iterate through one dictionary and do member checks on the other one. – poke Jun 13 '15 at 12:58
  • There already is an answer that does that and SO rules recommend to post original answers only :) – Eli Korvigo Jun 13 '15 at 13:01
  • Sure, my comment wasn’t meant to be a “this is bad, you should do X instead”, just a general note on the performance of this solution :) – poke Jun 13 '15 at 13:02
4

# Updated Ans:

METHOD-1: Using Dictionary Views:

As Martijn suggested, We can use dictionary views to check this. dict.viewitems() acts as a set. We can perform various set operations on this like intersection, union etc. (Check this link.)

first.viewitems() <= second.viewitems()
True

We check if first is less than equal to second. This evaluating to True means first is a subset of second.

METHOD-2 Using issubset() operation of sets:

(DISCLAIMER: This method has some redundancy and requires all values to be hashable. Method-1 is suggested to be followed to handle all cases. Thanks Martijn for the suggestions.)

Use .items() attribute of a dictionary to get a list of (key,value) tuples and then use issubset() operation of sets.

This will check for both the keys and the equality..

>>> first  = {"one":"un", "two":"deux", "three":"trois"}
>>> second = {"one":"un", "two":"deux", "three":"trois", "foo":"bar"}

>>> set(first.items()).issubset(second.items())
True
Community
  • 1
  • 1
Rahul Gupta
  • 46,769
  • 10
  • 112
  • 126
  • 1
    Why use `list()` first and not use `set(first.items()).issubset(second.items())` directly? And in Python 3, `dict.items()` *supports the usecase directly*; `l1.items() < l2.items()` works just fine. Note that this requires all values to be hashable (using `set()` objects or dictionary views). – Martijn Pieters Jun 13 '15 at 13:06
  • Thanks Martijn. That step was unnecessary. Updated the ans! – Rahul Gupta Jun 13 '15 at 13:09
  • You still have redundancy here; in Python 3 `dict.items()` *already acts as a set*. In Python 2 you can get the same behaviour by using `dict.viewitems()`. And your approach still requires that the values are hashable, while dictionary views do not. – Martijn Pieters Jun 13 '15 at 13:21
  • Yeaa updated! thanks for the suggestions Martijn..:) – Rahul Gupta Jun 13 '15 at 15:16