In Python, why is a tuple hashable but not a list?

Question:

Here below when I try to hash a list, it gives me an error but works with a tuple. Guess it has something to do with immutability. Can someone explain this in detail ?

List

 x = [1,2,3]
 y = {x: 9}
  Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
 TypeError: unhashable type: 'list'

Tuple

z = (5,6)
y = {z: 89}
print(y)
{(5, 6): 89}
Asked By: Nagendra Kakarla

||

Answers:

Dicts and other objects use hashes to store and retrieve items really quickly. The mechanics of this all happens "under the covers" – you as the programmer don’t need to do anything and Python handles it all internally. The basic idea is that when you create a dictionary with {key: value}, Python needs to be able to hash whatever you used for key so it can store and look up the value quickly.

Immutable objects, or objects that can’t be altered, are hashable. They have a single unique value that never changes, so python can "hash" that value and use it to look up dictionary values efficiently. Objects that fall into this category include strings, tuples, integers and so on. You may think, "But I can change a string! I just go mystr = mystr + 'foo'," but in fact what this does is create a new string instance and assigns it to mystr. It doesn’t modify the existing instance. Immutable objects never change, so you can always be sure that when you generate a hash for an immutable object, looking up the object by its hash will always return the same object you started with, and not a modified version.

You can try this for yourself: hash("mystring"), hash(('foo', 'bar')), hash(1)

Mutable objects, or objects that can be modified, aren’t hashable. A list can be modified in-place: mylist.append('bar') or mylist.pop(0). You can’t safely hash a mutable object because you can’t guarantee that the object hasn’t changed since you last saw it. You’ll find that list, set, and other mutable types don’t have a __hash__() method. Because of this, you can’t use mutable objects as dictionary keys:

>>> hash([1,2,3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Eric Duminil‘s answer provides a great example of the unexpected behaviour that arises from using mutable objects as dictionary keys

Answered By: daveruinseverything

A hashset calculates the hash of an object and based on that hash, stores the object in the structure for fast lookup. As a result, by contract once an object is added to the dictionary, the hash is not allowed to change. Most good hash functions will depend on the number of elements and the elements itself.

A tuple is immutable, so after construction, the values cannot change and therefore the hash cannot change either (or at least a good implementation should not let the hash change).

A list on the other hand is mutable: one can later add/remove/alter elements. As a result the hash can change violating the contract.

So all objects that cannot guarantee a hash function that remains stable after the object is added, violate the contract and thus are no good candidates. Because for a lookup, the dictionary will first calculate the hash of the key, and determine the correct bucket. If the key is meanwhile changed, this could result in false negatives: the object is in the dictionary, but it can no longer be retrieved because the hash is different so a different bucket will be searched than the one where the object was originally added to.

Answered By: Willem Van Onsem

Because a list is mutable, while a tuple is not. When you store the hash of a value in, for example, a dict, if the object changes, the stored hash value won’t find out, so it will remain the same. The next time you look up the object, the dictionary will try to look it up by the old hash value, which is not relevant anymore.

To prevent that, python does not allow you to has mutable items.

Answered By: blue_note

I would like to add the following aspect as it’s not covered by other answers already.

There’s nothing wrong about making mutable objects hashable, it’s just not unambiguous and this is why it needs to be defined and implemented consistently by the programmer himself (not by the programming language).

Note that you can implement the __hash__ method for any custom class which allows its instances to be stored in contexts where hashable types are required (such as dict keys or sets).

Hash values are usually used to decide if two objects represent the same thing. So consider the following example. You have a list with two items: l = [1, 2]. Now you add an item to the list: l.append(3). And now you must answer the following question: Is it still the same thing? Both – yes and no – are valid answers. “Yes”, it is still the same list and “no”, it has not the same content anymore.

So the answer to this question depends on you as the programmer and so it is up to you to manually implement hash methods for your mutable types.

Answered By: a_guest

Here are examples why it might not be a good idea to allow mutable types as keys. This behaviour might be useful in some cases (e.g. using the state of the object as a key rather than the object itself) but it also might lead to suprising results or bugs.

Python

It’s possible to use a numeric list as a key by defining __hash__ on a subclass of list :

class MyList(list):
    def __hash__(self):
        return sum(self)

my_list = MyList([1, 2, 3])

my_dict = {my_list: 'a'}

print(my_dict.get(my_list))
# a

my_list[2] = 4  # __hash__() becomes 7
print(next(iter(my_dict)))
# [1, 2, 4]
print(my_dict.get(my_list))
# None
print(my_dict.get(MyList([1,2,3])))
# None

my_list[0] = 0  # __hash_() is 6 again, but for different elements
print(next(iter(my_dict)))
# [0, 2, 4]
print(my_dict.get(my_list))
# 'a'

Ruby

In Ruby, it’s allowed to use a list as a key. A Ruby list is called an Array and a dict is a Hash, but the syntax is very similar to Python’s :

my_list = [1]
my_hash = { my_list => 'a'}
puts my_hash[my_list]
#=> 'a'

But if this list is modified, the dict doesn’t find the corresponding value any more, even if the key is still in the dict :

my_list << 2

puts my_list
#=> [1,2]

puts my_hash.keys.first
#=> [1,2]

puts my_hash[my_list]
#=> nil

It’s possible to force the dict to calculate the key hashes again :

my_hash.rehash
puts my_hash[my_list]
#=> 'a'
Answered By: Eric Duminil

Based on Python Glossary

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__() method). Hashable objects which compare equal must have the same hash value.

All of Python’s immutable built-in objects are hashable; mutable containers (such as lists or dictionaries) are not.

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