list vs tuple for a dict key- why aren't both hashable?

Wells wells at submute.net
Sun Nov 8 18:01:15 EST 2009


On Nov 8, 2:42 pm, Mick Krippendorf <mad.m... at gmx.de> wrote:
> Wells wrote:
> > I'm not quite understanding why a tuple is hashable but a list is not.
>
> The short answer has already been given. Here is the long answer:
>
> For objects p and q, p==q implies hash(p)==hash(q). It is essential for
> dicts and sets that objects used as keys/elements uphold this law, and
> also that, for an object o, hash(o) doesn't change during the lifetime
> of o. What if you write a class that doesn't? Let's see:
>
> >>> class Thing(object):
>
> ...     def __init__(self, value):
> ...         self.value = value
> ...     def __eq__(self, other):
> ...         return self.value == other.value
> ...     def __hash__(self):
> ...         return hash(self.value)
> ...     def __repr__(self):
> ...         return "Thing(%s)" % self.value
> ...
>
> >>> thinglist = [Thing(i) for i in xrange(10)]
>
> >>> thingdict = dict((y,x) for x,y in enumerate(thinglist))
>
> >>> print thingdict[thinglist[5]]
> 5
>
> >>> thinglist[5].value = 99
>
> >>> print thingdict[thinglist[5]]
>
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> KeyError: Thing(99)
>
> What happened? __eq__ and __hash__ both depend on a mutable attribute,
> and when that attribute was changed, their outcome changed as well. If a
> Thing is stored as key in a dict and later it's value attribute is
> changed, it cannot be found anymore. Too bad.
>
> BTW, this doesn't work either:
>
> >>> print thingdict[Thing(5)]
>
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> KeyError: Thing(5)
>
> wheras this works:
>
> >>> print thingdict[Thing(6)]
>
> 6
>
> What has this got to do with lists and tuples? Well, two tuples x and y
> are considered equal, iff:
>
> >>> all(a==b for a,b in zip(x,y))
>
> Also, by the law above, hash(x)==hash(y). Since tuples are immutable, x
> and y (and hash(x) and hash(y)) will be equal as long as they live.
>
> Lists have the same properties regarding equality, but are mutable.
> If we'd use a list as key in a dict and append an element to the list,
> __eq__ and __hash__ would return something different than before the
> append. The list couldn't be found anymore, just like the instance of
> the broken Thing class above. Lists are not hashable, because it would
> lead to untraceable bugs otherwise, and it would confuse beginners.
>
> This also teaches a lesson on how to implement __eq__ and __hash__, if
> you must. Just make sure your objects always do uphold the law above,
> and do not change in respect to __hash__ during their lifetime. OTOH it
> is possible to do otherwise, as long as you don't try to use these
> objects as elements of a set or keys in a dictionary. But then, why
> would you bother to implement your own __hash__ method?
>
> Regards,
> Mick.

Thanks Mick - this was very enlightening!



More information about the Python-list mailing list