107

I'm receiving the following error in my program. The traceback:

Traceback (most recent call last):
File "C:\Python33\Archive\PythonGrafos\Alpha.py", line 126, in <module>
menugrafos()
File "C:\Python33\Archive\PythonGrafos\Alpha.py", line 97, in menugrafos
zetta = Beta.caminhografo(grafo,va,vb)
File "C:\Python33\Archive\PythonGrafos\Beta.py", line 129, in caminhografo
if ([vo, a]) in vat == ([vo,vq]) in vat:
TypeError: unhashable type: 'list'

The program is meant to make an adjacency list which works fine, and then proceed to search if there is a path between vertex va and vb. I used a dictionary of lists in collection/defaultdict to adequately append adjacent vertex.

The problem is in the if clauses after the list are created at the end of the program. I can't find a way to properly use the if-clauses with the dict to see if there is a valid path between vertex. Also, grafo is a graph class.

Here is the code:

class graph:
    v = 0
    a = 0
    node = []

class vertex:
    ta = []
    adj = {}
    
def caminhografo(grafo, va, vb):
    vat = defaultdict(list)
    i = 0
    a = 0
    z = 0
    vo = int(va)
    vq = int(vb)
    vz = int(va)
    vw = int(vb)
    x = len(grafo.node)
    if vz < vw:
        for vz in range (vw+1):
            a = 0
            x = len(grafo.node)
            for a in range (x):
                if [int(vz),int(a)] in grafo.node:
                    vat[vz].append(a)                   
    if vz > vw:
        while vz > vw:
            a = 0
            x = len(grafo.node)
            for a in range (x):
                if[int(va),int(a)] in grafo.node:
                    vat[vz].append(a)
            vz = vz - 1
    a = 0
    x = len(grafo.node)
    print(vat)
    for a in range (x):
       if ([vo, a]) in vat == ([vo,vq]) in vat:
           print("""
    ==============================================
               Existe Caminho
    ==============================================
    """)
           break
       elif ([vo,a]) in vat:
           vo = a
       else:           
           print("""
    ==============================================
             Não Existe Caminho
    ==============================================
        """)
           break
cottontail
  • 10,268
  • 18
  • 50
  • 51
Rex
  • 1,201
  • 5
  • 12
  • 11

2 Answers2

169

The problem is that you can't use a list as the key in a dict, since dict keys need to be immutable. Use a tuple instead.

This is a list:

[x, y]

This is a tuple:

(x, y)

Note that in most cases, the ( and ) are optional, since , is what actually defines a tuple (as long as it's not surrounded by [] or {}, or used as a function argument).

You might find the section on tuples in the Python tutorial useful:

Though tuples may seem similar to lists, they are often used in different situations and for different purposes. Tuples are immutable, and usually contain an heterogeneous sequence of elements that are accessed via unpacking (see later in this section) or indexing (or even by attribute in the case of namedtuples). Lists are mutable, and their elements are usually homogeneous and are accessed by iterating over the list.

And in the section on dictionaries:

Unlike sequences, which are indexed by a range of numbers, dictionaries are indexed by keys, which can be any immutable type; strings and numbers can always be keys. Tuples can be used as keys if they contain only strings, numbers, or tuples; if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key. You can’t use lists as keys, since lists can be modified in place using index assignments, slice assignments, or methods like append() and extend().


In case you're wondering what the error message means, it's complaining because there's no built-in hash function for lists (by design), and dictionaries are implemented as hash tables.

Brendan Long
  • 53,280
  • 21
  • 146
  • 188
  • Is the tuple considered part of the dict list, or it's also the key? as the program is completely ignorning the ifs as if they were always false. – Rex Oct 15 '13 at 01:35
  • @Rex I don't think I understand your question. If you use a tuple as key, then it will be the key, just like any other key. `(some, tuple) in some_dict` should work, assuming `(some, tuple)` is actually a key in `some_dict`. Make sure your actually added the keys you think you added. It may be better to play around with simpler dictionaries in the interpreter if you're having trouble understanding how they work. – Brendan Long Oct 15 '13 at 01:36
  • So the tuple will be read just as keys? If i'm looking for (key1, element_in_key) it won't work? – Rex Oct 15 '13 at 01:42
  • @Rex The easiest way to answer your questions is probably to try things in the Python interpreter. The short version is, if you store a key like, `some_dict[key] = something`, then `key in some_dict` will be true. It doesn't matter what the key is (as long as it's hashable). – Brendan Long Oct 15 '13 at 02:28
  • So by using a dictionary of lists, i block myself from getting list values from the keys in the if? In that case, my if will never be true in any case because i'm looking for a mutable variable? So it's better to use another format to store the adjacency of the vertex. – Rex Oct 15 '13 at 02:36
  • @Rex There's no question of what happens if you use a mutable type as a key, because you *can't*. Just use a tuple instead. You can even convert a list to a tuple with [`tuple()`](http://docs.python.org/3/library/functions.html#func-tuple) if you need to. You can also use a list as the value in a dictionary, just not the key. So, a dictionary can handle any use-case, it just can't do it *as a list*, it has to be a tuple. – Brendan Long Oct 15 '13 at 03:23
  • @Rex Specifically, you can do this: `my_dict[x] = [y, z]`, but you can't do: `my_dict[[y, z]] = x`. You can do the same thing with a tuple though: `my_dict[(y, z)] = x`. Or you can convert a list (or any iterable) to a tuple: `my_dict[tuple(any_itable)] = x`. – Brendan Long Oct 15 '13 at 03:27
  • I understand, but the if is still being ignored even after i messed around with the ()[], like (a,[b]). I don't know why its not working. – Rex Oct 15 '13 at 20:58
  • @Rex If your code runs without throwing an exception, then this question is answered. Please create a new question for your new problem. Answering followup questions in the comments is extremely difficult, and prevents you from getting help from other site members. – Brendan Long Oct 15 '13 at 21:40
  • @Brendan "Note that in most cases, the ( and ) are optional..." parenthesis are also required in tuples that are arguments to functions, like `f((x,y))`. – Miguel Feb 18 '15 at 17:56
  • Thanks for this very helpful answer. I already understood that I needed to use a tuple rather than a list; what I hadn't realized (and which you might want to emphasize (in italic?) in your answer to make it even better, is the part about "if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key." My tuple had a list in it, which I needed to make into a tuple as well. – Alex Dupuy Dec 24 '15 at 17:26
  • @AlexDupuy Good idea. I bolded that part too. – Brendan Long Dec 28 '15 at 20:08
  • Thanks for explaining the reason and solution! Worked like a charm in my case :) – MatthewRock Feb 01 '16 at 15:19
  • My case: I was getting a value of a dict through a list (`{}.get(['test'])`) –  Oct 25 '19 at 00:50
2

If you arrived at this post because you got the error in the title, apart from OP's problem (where a list was being used a key to a dict), there are a couple more cases where this may occur.

1. A list is being passed into a set

Just like why lists cannot be dictionary keys, lists cannot be a set element. If a tuple is to be added to it, is shouldn't contain a list either.

s = {(1, 2), [3, 4]}    # <---- TypeError: unhashable type: 'list'
s = {(1, 2), (3, 4)}    # <---- OK

s.add((5, [6]))         # <---- TypeError because the element to be added contains a list
s.add((5, 6))           # <---- OK because (5, 6) is a tuple
2. Pandas groupby on lists

Another common way this error occurs is if a pandas dataframe column stores a list and is used as a grouper in a groupby operation. A solution is similar as above, convert the lists into tuples and groupby using the column of tuples.

import pandas as pd
df = pd.DataFrame({'group': [[1, 2], [3, 4], [5, 6]], 'value': [0, 1, 2]})

# group  value
# [1, 2]     0
# [3, 4]     1
# [5, 6]     2

df.groupby('group')['value'].mean()                  # <---- TypeError
df.groupby(df['group'].agg(tuple))['value'].mean()   # <---- OK
#          ^^^^^^^^^^^^^^^^^^^^^^  <--- convert each list into a tuple
3. Pandas index/column labels contain a list

Pandas column label cannot be a list (because it is analogous to a dictionary key), so if you attempt to rename() it by a list, it will show this error. A solution is to convert the list into a tuple (or even into a MultiIndex).

df = pd.DataFrame({'group': range(3)})
df.rename(columns={'group': ['col', 'one']})               # TypeError
df.rename(columns={'group': ('col', 'one')})               # OK
df.columns = pd.MultiIndex.from_tuples([('col', 'one')])   # OK

Pandas index can contain a list as a value but if you try to index that row, it will throw this error. A solution is to convert the list into a tuple or simply "clean" the data (probably the index shouldn't contain a list/tuple to begin with) such as converting it into a MultiIndex.

df = pd.DataFrame({'group': range(3)}, index=[['a'], 'b', 'c'])
df.loc['b']           # TypeError
4. collections.Counter is called on an object containing a list

Because Counter creates a dict-like object, each value should be immutable for the very same reason, so if an object contains a list, this error will be shown. A solution is probably to convert the list into a tuple

from collections import Counter
lst = ['a', 'b', ['c']]
Counter(lst)                  # TypeError

Counter(['a', 'b', ('c',)])   # OK
cottontail
  • 10,268
  • 18
  • 50
  • 51