Como evitar o erro "RuntimeError: dicionário alterado de tamanho durante a iteração"?

258

Verifiquei todas as outras perguntas com o mesmo erro e ainda não encontrei uma solução útil = /

Eu tenho um dicionário de listas:

d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}

em que alguns dos valores estão vazios. No final da criação dessas listas, desejo remover essas listas vazias antes de retornar meu dicionário. Atualmente, estou tentando fazer o seguinte:

for i in d:
    if not d[i]:
        d.pop(i)

no entanto, isso está me dando o erro de tempo de execução. Estou ciente de que você não pode adicionar / remover elementos em um dicionário enquanto itera através dele ... o que seria uma maneira de contornar isso então?

user1530318
fonte

Respostas:

455

No Python 2.x, a chamada keysfaz uma cópia da chave que você pode iterar enquanto modifica dict:

for i in d.keys():

Observe que isso não funciona no Python 3.x porque keysretorna um iterador em vez de uma lista.

Outra maneira é usar listpara forçar uma cópia das chaves a serem feitas. Este também funciona no Python 3.x:

for i in list(d):
Mark Byers
fonte
1
Acredito que você quis dizer 'chamar keysfaz uma cópia das chaves pelas quais você pode interagir', ou seja, as pluralchaves, certo? Caso contrário, como é possível iterar sobre uma única chave? Eu não estou nit escolher a propósito, estou realmente interessado em saber se isso é realmente tecla ou teclas
HighOnMeat
6
Ou tupla em vez de lista, pois é mais rápido.
Brambor
8
Para esclarecer o comportamento do python 3.x, d.keys () retorna um iterável (não um iterador), o que significa que é uma visualização diretamente nas chaves do dicionário. O uso for i in d.keys()realmente funciona no python 3.x em geral , mas, como é iterativo em uma exibição iterável das chaves do dicionário, a chamada d.pop()durante o loop leva ao mesmo erro que você encontrou. for i in list(d)emula o comportamento levemente ineficiente do python 2 de copiar as chaves para uma lista antes da iteração, em circunstâncias especiais como a sua.
Michael Krebs
sua solução python3 não funciona quando você deseja excluir um objeto no dict interno. por exemplo, você tem um dic A e dict B no dict A. se você deseja excluir um objeto no dict B, ocorre um erro
Ali-T
1
No python3.x, list(d.keys())cria a mesma saída que list(d), chamando lista dictretorna as chaves. A keysligação (embora não seja tão cara) é desnecessária.
Sean Breckenridge
46

Basta usar a compreensão do dicionário para copiar os itens relevantes para um novo ditado

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = { k : v for k,v in d.iteritems() if v}
>>> d
{'a': [1], 'b': [1, 2]}

Para isso em Python 3

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = { k : v for k,v in d.items() if v}
>>> d
{'a': [1], 'b': [1, 2]}
Maria Zverina
fonte
11
d.iteritems()me deu um erro. Em d.items()vez disso, usei - usando python3
wcyn 12/12
4
Isso funciona para o problema colocado na pergunta do OP. No entanto, qualquer um que veio aqui depois de acessar este RuntimeError em código multithread, esteja ciente de que o GIL do CPython também pode ser liberado no meio da compreensão da lista e você deve corrigi-lo de maneira diferente.
Yirkha
40

Você só precisa usar "copiar":

Dessa maneira, você itera sobre os campos originais do dicionário e, em tempo real, pode alterar o ditado desejado (dict). É um trabalho em cada versão python, por isso é mais claro.

In [1]: d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}

In [2]: for i in d.copy():
   ...:     if not d[i]:
   ...:         d.pop(i)
   ...:         

In [3]: d
Out[3]: {'a': [1], 'b': [1, 2]}
Alon Elharar
fonte
Trabalhou! Obrigado.
Guilherme Carvalho Lithg 18/09/19
13

Eu tentaria evitar a inserção de listas vazias em primeiro lugar, mas geralmente usaria:

d = {k: v for k,v in d.iteritems() if v} # re-bind to non-empty

Se anterior a 2.7:

d = dict( (k, v) for k,v in d.iteritems() if v )

ou apenas:

empty_key_vals = list(k for k in k,v in d.iteritems() if v)
for k in empty_key_vals:
    del[k]
Jon Clements
fonte
+1: a última opção é interessante porque apenas copia as chaves dos itens que precisam ser excluídos. Isso pode oferecer melhor desempenho se apenas um pequeno número de itens precisar ser excluído em relação ao tamanho do ditado.
precisa
@ MarkByers, sim - e se um grande número o fizer, vincular novamente o ditado a um novo filtrado é uma opção melhor. É sempre a expectativa de como a estrutura deve funcionar
Jon Clements
4
Um perigo com a religação é se, em algum lugar do programa, houvesse um objeto que continha uma referência ao ditado antigo, ele não veria as alterações. Se você tem certeza de que não é esse o caso, então é claro ... essa é uma abordagem razoável, mas é importante entender que não é o mesmo que modificar o ditado original.
precisa
@ MarkByers ponto extremamente bom - você e eu sabemos disso (e muitos outros), mas não é óbvio para todos. E eu vou colocar dinheiro na mesa que não tenha também mordido na :) traseira
Jon Clements
O ponto de evitar inserir as entradas vazias é muito bom.
Magnus Bodin
12

Para Python 3:

{k:v for k,v in d.items() if v}
ucyo
fonte
Agradável e conciso. Trabalhou para mim no Python 2.7 também.
donrondadon
6

Você não pode percorrer um dicionário enquanto ele estiver sendo alterado durante o loop for. Faça um casting para listar e itere sobre essa lista, funciona para mim.

    for key in list(d):
        if not d[key]: 
            d.pop(key)
Alvaro Romero Diaz
fonte
1

Para situações como essa, eu gosto de fazer uma cópia profunda e fazer um loop através dessa cópia enquanto modifica o ditado original.

Se o campo de pesquisa estiver em uma lista, você poderá enumerar no loop for da lista e especificar a posição como índice para acessar o campo no ditado original.

Ajayi Oluwaseun Emmanuel
fonte
1

Isso funcionou para mim:

dict = {1: 'a', 2: '', 3: 'b', 4: '', 5: '', 6: 'c'}
for key, value in list(dict.items()):
    if (value == ''):
        del dict[key]
print(dict)
# dict = {1: 'a', 3: 'b', 6: 'c'}  

A conversão dos itens do dicionário para a lista cria uma lista de seus itens, para que você possa iterar sobre ele e evitar o RuntimeError.

singrium
fonte
1

dictc = {"stName": "asas"} chaves = dictc.keys () para a chave na lista (chaves): dictc [key.upper ()] = 'Novo valor' print (str (dictc))

vaibhav.patil
fonte
0

O motivo do erro de tempo de execução é que você não pode iterar através de uma estrutura de dados enquanto sua estrutura está sendo alterada durante a iteração.

Uma maneira de conseguir o que você está procurando é usar a lista para anexar as chaves que você deseja remover e, em seguida, usar a função pop no dicionário para remover a chave identificada enquanto percorre a lista.

d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}
pop_list = []

for i in d:
        if not d[i]:
                pop_list.append(i)

for x in pop_list:
        d.pop(x)
print (d)
Rohit
fonte
0

O Python 3 não permite a exclusão ao iterar (usando o loop for acima). Existem várias alternativas para fazer; Uma maneira simples é mudar a seguinte linha

for i in x.keys():

Com

for i in list(x)
Hasham
fonte