Since lists are mutable, dict keys (and set members) must be hashed, and hashing mutable objects is a bad idea because the values of the hash function must be calculated based on the attributes of the instance.
In this answer, I will give some specific examples that hopefully add value over existing answers. Each understanding also applies to set elements.
Example 1 : hashing a mutable object, where the value of the hash function is based on the mutable characteristic of the object.
>>> class stupidlist(list): ... def __hash__(self): ... return len(self) ... >>> stupid = stupidlist([1, 2, 3]) >>> d = {stupid: 0} >>> stupid.append(4) >>> stupid [1, 2, 3, 4] >>> d {[1, 2, 3, 4]: 0} >>> stupid in d False >>> stupid in d.keys() False >>> stupid in list(d.keys()) True
After mutating stupid , it cannot be found in the Dictionary anymore because the hash has changed. Only a linear scan over the dict key list finds stupid .
Example 2 : ... but why not just a constant hash value?
>>> class stupidlist2(list): ... def __hash__(self): ... return id(self) ... >>> stupidA = stupidlist2([1, 2, 3]) >>> stupidB = stupidlist2([1, 2, 3]) >>> >>> stupidA == stupidB True >>> stupidA in {stupidB: 0} False
This is also not a good idea, because identical objects must be hashed the same way so that they can be found in dict or set .
Example 3 : ... well, what about persistent hashes in all cases ?!
>>> class stupidlist3(list): ... def __hash__(self): ... return 1 ... >>> stupidC = stupidlist3([1, 2, 3]) >>> stupidD = stupidlist3([1, 2, 3]) >>> stupidE = stupidlist3([1, 2, 3, 4]) >>> >>> stupidC in {stupidD: 0} True >>> stupidC in {stupidE: 0} False >>> d = {stupidC: 0} >>> stupidC.append(5) >>> stupidC in d True
Everything seems to work as expected, but think about what happens: when all instances of your class produce the same hash value, you will have a hash value conflict if there are more than two in the dict or set instances as keys.
To find the required instance using my_dict[key] or key in my_dict (or item in my_set ), you need to perform as many equality checks as there are stupidlist3 examples in dict keys (in the worst case). At the moment, the goal of the dictionary - search O (1) - is completely defeated. This is demonstrated in the following cases (done with IPython).
Some terms for example 3
>>> lists_list = [[i] for i in range(1000)] >>> stupidlists_set = {stupidlist3([i]) for i in range(1000)} >>> tuples_set = {(i,) for i in range(1000)} >>> l = [999] >>> s = stupidlist3([999]) >>> t = (999,) >>> >>> %timeit l in lists_list 25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) >>> %timeit s in stupidlists_set 38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) >>> %timeit t in tuples_set 77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
As you can see, the membership test in our stupidlists_set even slower than a linear scan of the entire lists_list , while you have the expected ultra-fast search time (coefficient 500) in the set without a lot of hash collisions.
TL; DR: you can use tuple(yourlist) as dict keys, because tuples are immutable and hashed.