Inconsistent behaviour between dict.values() and dict.keys() equality in Python 3.x and Python 2.7

Question:

I have found that comparing the results of the keys() and values() methods of the dict built-in to themselves results in inconsistent results:

instance = {'one': 1}

instance.values() == instance.values() # Returns False
instance.keys() == instance.keys()     # Returns True

Running the above code in Python 2.7 will return True for both calls, leading me to believe that there is some implementation detail in Python 3’s dict_values that causes this strange behaviour.

Is there a reason for this behaviour or have i stumbled upon some obscure bug?

Asked By: thom747

||

Answers:

The short answer: class dict_values doesn’t have a __eq__ method implemented, but class dict_keys does:

>>> d.values().__eq__(d.values())
NotImplemented
>>> d.keys().__eq__(d.keys())
True

Therefore, the d.values()‘s == comparison evaluates to False.

The longer answer of why it wasn’t implemented is a different one and can be seen with a little more digging on the documentation of dict-view objects. This part seems especially relevant (emphasis mine):

Keys views are set-like since their entries are unique and hashable.
If all values are hashable, so that (key, value) pairs are unique and
hashable, then the items view is also set-like. (Values views are not
treated as set-like since the entries are generally not unique.) For
set-like views, all of the operations defined for the abstract base
class collections.abc.Set are available (for example, ==, <, or ^).

Since keys must be unique, it makes sense that they are set-like and are supported with the class operations of collections.Set. Values are not set-like due to non-uniqueness.

In Python 2.7, d.keys() and d.values() both return a list per the documentation, so you’ll get ordinary list comparison if you try ==. Of course, since the lists aren’t guaranteed to be in any particular order, the comparison doesn’t actually do what you want, but you can perform the comparison if you really want to:

x = {0: 'x', 16: 'x'}
y = {16: 'x', 0: 'x'}

# prints False twice - you'd get True on Python 3.
print x.keys() == y.keys()
print x.items() == y.items()

If you used viewkeys and viewvalues as mentioned in the documentation of dict-view objects in Python2.7, then you can expect similar behaviour to Python 3:

# Python 2.7
from collections import Set
# in Python 3.x this would be from collections.abc import Set

d = {"one": 1}

print isinstance(d.viewkeys(), Set)
# True

print isinstance(d.viewvalues(), Set)
# False

print d.viewkeys() == d.viewkeys()
# True

print d.viewvalues() == d.viewvalues()
# False
Answered By: r.ook
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.