Jak pokonać TypeError: unhashable type: 'list'

97

Próbuję pobrać plik, który wygląda następująco:

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

I użyj słownika, aby wynik wyglądał następująco

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

To jest to, czego próbowałem

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()

Ciągle otrzymuję TypeError: unhashable type: 'list'. Wiem, że klucze w słowniku nie mogą być listami, ale próbuję przekształcić moją wartość w listę, a nie klucz. Zastanawiam się, czy gdzieś popełniłem błąd.

Keenan
źródło

Odpowiedzi:

56

Jak wskazują inne odpowiedzi, błąd wynika z tego k = list[0:j], gdzie klucz jest konwertowany na listę. Jedną z rzeczy, które możesz wypróbować, jest przerobienie kodu, aby wykorzystać tę splitfunkcję:

# 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']}

Zauważ, że jeśli używasz Pythona 3.x, będziesz musiał dokonać drobnych zmian, aby działał poprawnie. Jeśli otworzysz plik za pomocą rb, musisz użyć line = line.split(b'x')(co zapewnia, że ​​dzielisz bajt za pomocą odpowiedniego typu ciągu). Możesz także otworzyć plik za pomocą with open('filename.txt', 'rU') as f:(lub nawet with open('filename.txt', 'r') as f:) i powinno działać dobrze.

RocketDonkey
źródło
32

Uwaga: ta odpowiedź nie odpowiada wprost na zadane pytanie. inne odpowiedzi to robią. Ponieważ pytanie jest specyficzne dla scenariusza, a podniesiony wyjątek jest ogólny , ta odpowiedź wskazuje na ogólny przypadek.

Wartości skrótu to po prostu liczby całkowite, które są używane do szybkiego porównywania kluczy słownika podczas wyszukiwania w słowniku.

Wewnętrznie hash()metoda wywołuje __hash__()metodę obiektu, która jest ustawiona domyślnie dla dowolnego obiektu.

Konwersja listy zagnieżdżonej na zestaw

>>> 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'

Dzieje się tak z powodu listy wewnątrz listy, która jest listą, której nie można zaszyfrować. Które można rozwiązać, przekształcając wewnętrzne listy zagnieżdżone w krotkę ,

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

Jawne haszowanie zagnieżdżonej listy

>>> 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

Rozwiązaniem pozwalającym uniknąć tego błędu jest zmiana struktury listy tak, aby zawierała zagnieżdżone krotki zamiast list.

Wszystko jest Vаиітy
źródło
4
co jeśli lista jest za duża? Wygląda na to, że jest to dobre rozwiązanie, ale nie dość ogólne
msh855
1
@ msh855 Czy istnieje limit rozmiaru? Przetestowałem słownik z krotką o rozmiarze 100 000 i działał dobrze (używam Pythona 3.6)
Sreram
19

Próbujesz użyć k(czyli listy) jako klucza do d. Listy są zmienne i nie mogą być używane jako klucze dyktowania.

Ponadto nigdy nie inicjalizujesz list w słowniku z powodu tego wiersza:

if k not in d == False:

Który powinien być:

if k not in d == True:

Który właściwie powinien być:

if k not in d:
Gra Jesse
źródło
6

Powodem, dla którego otrzymujesz unhashable type: 'list'wyjątek, jest to, że k = list[0:j]ustawia księ jako „wycinek” listy, która jest logicznie kolejną, często krótszą listą. Potrzebujesz tylko pierwszej pozycji na liście, tak napisanej k = list[0]. To samo, dla v = list[j + 1:]którego powinno być tylko v = list[2]dla trzeciego elementu listy zwróconego z wywołania readline.split(" ").

Zauważyłem kilka innych prawdopodobnych problemów z kodem, z których kilka wymienię. Duży jeden jest nie chcą (re) zainicjować dz d = {}dla każdej linii czytać w pętli. Innym jest to, że generalnie nie jest dobrym pomysłem nazywanie zmiennych tak samo jak wszystkie typy wbudowane, ponieważ uniemożliwi to dostęp do jednego z nich, jeśli tego potrzebujesz - i jest mylące dla innych, którzy są przyzwyczajeni do nazwy określające jeden z tych standardowych elementów. Z tego powodu powinieneś zmienić nazwę listzmiennej na inną, aby uniknąć takich problemów.

Oto działająca wersja twojego z tymi zmianami. Zastąpiłem również ifwyrażenie wyrażenia, którego użyłeś do sprawdzenia, czy klucz był już w słowniku, a teraz użyj metody słownika, setdefault()aby osiągnąć to samo trochę bardziej zwięźle.

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))

Wynik:

d: {'AAA': ['111', '112'], 'AAC': ['123'], 'AAB': ['111']}
martineau
źródło
0

TypeErrorSię dzieje, ponieważ kjest to lista, ponieważ jest tworzony przy użyciu plasterek z innej listy z linii k = list[0:j]. Prawdopodobnie powinno to być coś w stylu k = ' '.join(list[0:j]), więc zamiast tego masz ciąg.

Oprócz tego, twoje ifstwierdzenie jest nieprawidłowe, jak zauważyła odpowiedź Jessego, która powinna brzmieć if k not in dlub if not k in d(wolę to drugie).

Czyścisz również słownik po każdej iteracji, ponieważ masz d = {}wewnątrz forpętli.

Zauważ, że nie powinieneś także używać listani filejako nazw zmiennych, ponieważ będziesz maskował wbudowane.

Oto jak przepisałbym twój kod:

d = {}
with open("filename.txt", "r") as input_file:
    for line in input_file:
        fields = line.split()
        j = fields.index("x")
        k = " ".join(fields[:j])
        d.setdefault(k, []).append(" ".join(fields[j+1:]))

Powyższa dict.setdefault()metoda zastępuje if k not in dlogikę z kodu.

Andrew Clark
źródło
chociaż masz pełne prawo do preferencji, not k in dmoże zmylić nowicjusza (not k) in d, ponieważ k not in dnie ma dwuznaczności
Jesse the Game
Twierdziłbym nawet, że jest to sposób „pytoniczny”, ponieważ not injest wymieniony jako operator .
Jesse the Game
Tak, myślę, że moje preferencje prawdopodobnie wynikają najpierw z nauki innych języków, gdzie w przypadku czegoś takiego jak test ograniczania nie miałbyś operatorów do tego, więc zrobiłbyś coś podobnego !a.contains(b). not inmoże być bardziej pythonowe, po prostu uważam, że koncepcja dwóch operatorów słów jest bardziej zagmatwana niż użycie odwrotności w wyrażeniu boolowskim.
Andrew Clark,
-1
    python 3.2

    with open("d://test.txt") as f:
              k=(((i.split("\n"))[0].rstrip()).split() for i in f.readlines())
              d={}
              for i,_,v in k:
                      d.setdefault(i,[]).append(v)
Raton
źródło