Python: TypeError: tipo inalterável: 'lista'

94

Estou tentando pegar um arquivo parecido com este

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

E use um dicionário para que a saída fique assim

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

Isso é o que eu tentei

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

Eu continuo recebendo um TypeError: unhashable type: 'list'. Eu sei que as chaves em um dicionário não podem ser listas, mas estou tentando transformar meu valor em uma lista, não na chave. Estou me perguntando se cometi um erro em algum lugar.

Keenan
fonte

Respostas:

56

Conforme indicado pelas outras respostas, o erro é devido a k = list[0:j], onde sua chave é convertida em uma lista. Uma coisa que você pode tentar é retrabalhar seu código para aproveitar as vantagens da splitfunção:

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

Observe que, se estiver usando o Python 3.x, você terá que fazer um pequeno ajuste para que funcione corretamente. Se você abrir o arquivo com rb, você precisará usar line = line.split(b'x')(o que garante que você está dividindo o byte com o tipo apropriado de string). Você também pode abrir o arquivo usando with open('filename.txt', 'rU') as f:(ou mesmo with open('filename.txt', 'r') as f:) e deve funcionar bem.

RocketDonkey
fonte
Eu tentei isso e recebo TypeError: type str não suporta a API de buffer na linha "line = line.split ('x')"
Keenan
1
@ user1871081 Ah, você está usando Python 3.x? Vou postar uma atualização que deve funcionar com isso.
RocketDonkey
31

Observação: esta resposta não responde explicitamente à pergunta feita. as outras respostas fazem isso. Como a pergunta é específica para um cenário e a exceção levantada é geral , essa resposta aponta para o caso geral.

Os valores hash são apenas números inteiros usados ​​para comparar as chaves do dicionário durante uma pesquisa no dicionário rapidamente.

Internamente, o hash()método chama o __hash__()método de um objeto que é definido por padrão para qualquer objeto.

Converter uma lista aninhada em um conjunto

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

Isso acontece por causa da lista dentro de uma lista que é uma lista que não pode ser hash. Que pode ser resolvido convertendo as listas aninhadas internas em uma tupla ,

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

Hashing explicitamente de uma lista aninhada

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

A solução para evitar esse erro é reestruturar a lista para ter tuplas aninhadas em vez de listas.

All ѕ Vаиітy
fonte
4
e se a lista for muito grande? parece que é uma boa solução, mas não é geral o suficiente
msh855 de
1
@ msh855 existe algum limite de tamanho? Testei o dicionário com uma tupla de tamanho 100.000 e funcionou bem para mim (estou usando o python 3.6)
Sreram
18

Você está tentando usar k(que é uma lista) como uma chave para d. As listas são mutáveis ​​e não podem ser usadas como chaves de ditado.

Além disso, você nunca inicializa as listas no dicionário, por causa desta linha:

if k not in d == False:

Que deve ser:

if k not in d == True:

Que deveria ser:

if k not in d:
Jesse o jogo
fonte
5

A razão pela qual você está recebendo a unhashable type: 'list'exceção é porque os k = list[0:j]conjuntos ksão uma "fatia" da lista, que é logicamente outra lista, geralmente mais curta. O que você precisa é obter apenas o primeiro item da lista, escrito assim k = list[0]. O mesmo para o v = list[j + 1:]qual deve ser apenas v = list[2]para o terceiro elemento da lista retornado da chamada para readline.split(" ").

Percebi vários outros problemas prováveis ​​com o código, dos quais mencionarei alguns. Um grande problema é que você não deseja (re) inicializar dcom d = {}para cada linha lida no loop. Outra é geralmente não é uma boa ideia nomear variáveis ​​da mesma forma que qualquer um dos tipos integrados, porque isso o impedirá de acessar um deles se precisar - e é confuso para outros que estão acostumados com o nomes que designam um desses itens padrão. Por esse motivo, você deve renomear sua variável listvariável para evitar problemas como esse.

Aqui está uma versão funcional do seu com essas alterações, também simplifiquei a ifexpressão de instrução que você tinha, que verifica se a chave já está no dicionário - há maneiras implícitas ainda mais curtas de fazer esse tipo de coisa, mas usando um condicional declaração está bem por enquanto.

d = {}
file = open("filename.txt", "r")
readline = file.readline().rstrip()
while readline:
    lst = readline.split(" ") # Split into sequence like ['AAA', 'x', '111'].
    k = lst[0]  # First item.
    v = lst[2]  # Third item.
    if k not in d:  # New key?
        d[k] = []  # Initialize its associated value to an empty list.
    d[k].append(v)
    readline = file.readline().rstrip()

file.close()  # Done reading file.
print('d: {}'.format(d))

Resultado:

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

O TypeErrorestá acontecendo porque ké uma lista, uma vez que é criada a partir de uma fatia de outra lista com a linha k = list[0:j]. Provavelmente deve ser algo assim k = ' '.join(list[0:j]), então você tem uma string em vez.

Além disso, sua ifafirmação está incorreta, conforme observado pela resposta de Jesse, que deveria ser if k not in dou if not k in d(eu prefiro a última).

Você também está limpando seu dicionário em cada iteração, já que está d = {}dentro de seu forloop.

Observe que você também não deve usar listou filecomo nomes de variáveis, pois estará mascarando os builtins.

Aqui está como eu reescreveria seu código:

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

O dict.setdefault()método acima substitui a if k not in dlógica do seu código.

Andrew Clark
fonte
embora a preferência seja seu total direito, not k in dpode confundir um novato (not k) in d, pois , embora k not in dnão tenha ambigüidade
Jesse the Game
Eu até argumentaria que é a forma 'pítônica' not inlistada como um operador .
Jesse the Game
Sim, acho que provavelmente minha preferência vem de aprender outras línguas primeiro, onde para algo como um teste de contenção você não teria operadores para isso, então você faria algo como !a.contains(b). not inpode ser mais pythônico, só acho o conceito de operadores de duas palavras mais confuso do que usar um inverso em uma expressão booleana.
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
fonte