How to overcome TypeError: unhashable type: 'list'

I'm trying to take a file that looks like this:

AAA x 111
AAB x 111
AAA x 112
AAC x 123
...

And use a dictionary to so that the output looks like this

{AAA: ['111', '112'], AAB: ['111'], AAC: [123], ...}

This is what I've tried

file = open("filename.txt", "r") 
readline = file.readline().rstrip()
while readline!= "":
    list = []
    list = readline.split(" ")
    j = list.index("x")
    k = list[0:j]
    v = list[j + 1:]
    d = {}
    if k not in d == False:
        d[k] = []
    d[k].append(v)
    readline = file.readline().rstrip()

I keep getting a TypeError: unhashable type: 'list'. I know that keys in a dictionary can't be lists but I'm trying to make my value into a list not the key. I'm wondering if I made a mistake somewhere.


Solution 1:

As indicated by the other answers, the error is to due to k = list[0:j], where your key is converted to a list. One thing you could try is reworking your code to take advantage of the split function:

# Using with ensures that the file is properly closed when you're done
with open('filename.txt', 'rb') as f:
  d = {}
  # Here we use readlines() to split the file into a list where each element is a line
  for line in f.readlines():
    # Now we split the file on `x`, since the part before the x will be
    # the key and the part after the value
    line = line.split('x')
    # Take the line parts and strip out the spaces, assigning them to the variables
    # Once you get a bit more comfortable, this works as well:
    # key, value = [x.strip() for x in line] 
    key = line[0].strip()
    value = line[1].strip()
    # Now we check if the dictionary contains the key; if so, append the new value,
    # and if not, make a new list that contains the current value
    # (For future reference, this is a great place for a defaultdict :)
    if key in d:
      d[key].append(value)
    else:
      d[key] = [value]

print d
# {'AAA': ['111', '112'], 'AAC': ['123'], 'AAB': ['111']}

Note that if you are using Python 3.x, you'll have to make a minor adjustment to get it work properly. If you open the file with rb, you'll need to use line = line.split(b'x') (which makes sure you are splitting the byte with the proper type of string). You can also open the file using with open('filename.txt', 'rU') as f: (or even with open('filename.txt', 'r') as f:) and it should work fine.

Solution 2:

Note: This answer does not explicitly answer the asked question. the other answers do it. Since the question is specific to a scenario and the raised exception is general, This answer points to the general case.

Hash values are just integers which are used to compare dictionary keys during a dictionary lookup quickly.

Internally, hash() method calls __hash__() method of an object which are set by default for any object.

Converting a nested list to a set

>>> a = [1,2,3,4,[5,6,7],8,9]
>>> set(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

This happens because of the list inside a list which is a list which cannot be hashed. Which can be solved by converting the internal nested lists to a tuple,

>>> set([1, 2, 3, 4, (5, 6, 7), 8, 9])
set([1, 2, 3, 4, 8, 9, (5, 6, 7)])

Explicitly hashing a nested list

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


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

>>> hash(tuple([1, 2, 3, tuple([4, 5,]), 6, 7]))
-7943504827826258506

The solution to avoid this error is to restructure the list to have nested tuples instead of lists.

Solution 3:

You're trying to use k (which is a list) as a key for d. Lists are mutable and can't be used as dict keys.

Also, you're never initializing the lists in the dictionary, because of this line:

if k not in d == False:

Which should be:

if k not in d == True:

Which should actually be:

if k not in d:

Solution 4:

The reason you're getting the unhashable type: 'list' exception is because k = list[0:j] sets k to be a "slice" of the list, which is logically another, often shorter, list. What you need is to get just the first item in list, written like so k = list[0]. The same for v = list[j + 1:] which should just be v = list[2] for the third element of the list returned from the call to readline.split(" ").

I noticed several other likely problems with the code, of which I'll mention a few. A big one is you don't want to (re)initialize d with d = {} for each line read in the loop. Another is it's generally not a good idea to name variables the same as any of the built-ins types because it'll prevent you from being able to access one of them if you need it — and it's confusing to others who are used to the names designating one of these standard items. For that reason, you ought to rename your variable list variable something different to avoid issues like that.

Here's a working version of your with these changes in it, I also replaced the if statement expression you used to check to see if the key was already in the dictionary and now make use of a dictionary's setdefault() method to accomplish the same thing a little more succinctly.

d = {}
with open("nameerror.txt", "r") as file:
    line = file.readline().rstrip()
    while line:
        lst = line.split() # Split into sequence like ['AAA', 'x', '111'].
        k, _, v = lst[:3]  # Get first and third items.
        d.setdefault(k, []).append(v)
        line = file.readline().rstrip()

print('d: {}'.format(d))

Output:

d: {'AAA': ['111', '112'], 'AAC': ['123'], 'AAB': ['111']}