objects as keys in python dictionaries

Question:

I’m trying to use an object as a key in a python dictionary, but it’s behaving in a way that I can’t quite understand.

First I create a dictionary with my object as the key:

package_disseminators = {
  ContentType("application", "zip", "http://other/property") : "one",
  ContentType("application", "zip") : "two"
}

Now create another object that is “the same” as one which is a key.

content_type = ContentType("application", "zip", "http://other/property")

I have given the ContentType object custom __eq__ and custom __str__ methods, such that the __eq__ method compares the __str__ values.

Now, some interactive python:

>>> for key in package_disseminators:
...     if key == content_type:
...             print "match"
...     else:
...             print "no match"
... 
no match
match

>>> content_type in package_disseminators.keys()
True

Ok, so it looks like my object is definitely being identified properly as a key, so:

>>> package_disseminators[content_type]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: (& (type="application/zip") (packaging="http://other/property") )

Er … ok? So content_type is in the package_disseminators.keys() list, but isn’t a key?

>>> package_disseminators.has_key(content_type)
False

Apparently not.

I presume that the comparison process that Python uses to determin equality differs between a straight “in” statement on a list and actually looking up a key in a dict, but I don’t know how. Any tips or insights?

Asked By: Richard J

||

Answers:

From the python documentation:

A dictionary’s keys are almost
arbitrary values. Values that are not
hashable, that is, values containing
lists, dictionaries or other mutable
types (that are compared by value
rather than by object identity) may
not be used as keys.

Hashable is defined as follows

An object is hashable if it has a hash
value which never changes during its
lifetime (it needs a __hash__()
method), and can be compared to other
objects (it needs an __eq__() or
__cmp__() method). Hashable objects which compare equal must have the same
hash value.

Hashability makes an object usable as
a dictionary key and a set member,
because these data structures use the
hash value internally.

So if you want to do this, you need to override the default __hash__() method on your object (see the comment from Steven Rumbalski below for further explanation).


>>> content_type in package_disseminators.keys()
True

I suppose this works because dict.keys() returns a list, and __contains__ probably checks for equality, but not for the same hashes.

Answered By: Reiner Gerecke

Since dicts are hash tables under the hood, you need to define both __eq__ and __hash__ for that to work.

The basic rule of thumb is:

  • For objects that __eq__ compares equal, __hash__ must return the same hash.

From your description, something like

def __hash__(self):
    return hash(str(self))

should work.

Answered By: nosklo
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.