Digamos que temos um dicionário Python d
e estamos iterando sobre ele assim:
for k,v in d.iteritems():
del d[f(k)] # remove some item
d[g(k)] = v # add a new item
( f
e g
são apenas algumas transformações de caixa preta.)
Em outras palavras, tentamos adicionar / remover itens d
durante a iteração usando iteritems
.
Isso está bem definido? Você poderia fornecer algumas referências para apoiar sua resposta?
(É bastante óbvio como consertar isso se estiver quebrado, então este não é o ângulo que procuro.)
python
dictionary
NPE
fonte
fonte
Respostas:
É explicitamente mencionado na página de documentação do Python (para Python 2.7 ) que
Da mesma forma para Python 3 .
O mesmo vale para
iter(d)
,d.iterkeys()
ed.itervalues()
, e vou mais longe dizendo que serve parafor k, v in d.items():
(não me lembro exatamente o quefor
serve, mas não ficaria surpreso se a implementação fosse chamadaiter(d)
).fonte
d.items()
deve ser seguro no Python 2.7 (o jogo muda com o Python 3), pois faz o que é essencialmente uma cópiad
, então você não está modificando o que está iterando.viewitems()
Alex Martelli pesa sobre isso aqui .
Pode não ser seguro mudar o recipiente (por exemplo, dict) enquanto faz um loop sobre o recipiente. Portanto,
del d[f(k)]
pode não ser seguro. Como você sabe, a solução alternativa é usard.items()
(para fazer um loop em uma cópia independente do contêiner) em vez ded.iteritems()
(que usa o mesmo contêiner subjacente).É normal modificar o valor em um índice existente do dicionário, mas inserir valores em novos índices (por exemplo
d[g(k)]=v
) pode não funcionar.fonte
Você não pode fazer isso, pelo menos com
d.iteritems()
. Eu tentei, e Python falhou comSe você usar
d.items()
, então funciona.No Python 3,
d.items()
é uma visão do dicionário, comod.iteritems()
no Python 2. Para fazer isso no Python 3, used.copy().items()
. Isso nos permitirá, da mesma forma, iterar sobre uma cópia do dicionário para evitar a modificação da estrutura de dados que estamos iterando.fonte
2to3
) de Py2d.items()
para Py3 élist(d.items())
, emborad.copy().items()
seja provavelmente de eficiência comparável.Eu tenho um grande dicionário contendo matrizes Numpy, então a coisa dict.copy (). Keys () sugerida por @ murgatroid99 não era viável (embora funcionasse). Em vez disso, acabei de converter keys_view em uma lista e funcionou bem (no Python 3.4):
for item in list(dict_d.keys()): temp = dict_d.pop(item) dict_d['some_key'] = 1 # Some value
Sei que isso não mergulha no reino filosófico do funcionamento interno do Python, como as respostas acima, mas fornece uma solução prática para o problema declarado.
fonte
O código a seguir mostra que isso não está bem definido:
def f(x): return x def g(x): return x+1 def h(x): return x+10 try: d = {1:"a", 2:"b", 3:"c"} for k, v in d.iteritems(): del d[f(k)] d[g(k)] = v+"x" print d except Exception as e: print "Exception:", e try: d = {1:"a", 2:"b", 3:"c"} for k, v in d.iteritems(): del d[f(k)] d[h(k)] = v+"x" print d except Exception as e: print "Exception:", e
O primeiro exemplo chama g (k) e lança uma exceção (o dicionário mudou de tamanho durante a iteração).
O segundo exemplo chama h (k) e não lança nenhuma exceção, mas resulta:
{21: 'axx', 22: 'bxx', 23: 'cxx'}
O que, olhando para o código, parece errado - eu esperava algo como:
{11: 'ax', 12: 'bx', 13: 'cx'}
fonte
{11: 'ax', 12: 'bx', 13: 'cx'}
mas o 21,22,23 deve lhe dar uma pista sobre o que realmente aconteceu: seu loop passou pelos itens 1, 2, 3, 11, 12, 13, mas não conseguiu pegar o segundo rodada de novos itens à medida que são inseridos na frente dos itens que você já havia iterado. Mudeh()
para voltarx+5
e você obterá outro x:'axxx'
etc. ou 'x + 3' e obterá o magnífico'axxxxx'
{11: 'ax', 12: 'bx', 13: 'cx'}
como você disse, então vou atualizar meu post sobre isso. De qualquer forma, esse comportamento claramente não está bem definido.Eu tive o mesmo problema e usei o seguinte procedimento para resolver esse problema.
A lista Python pode ser iterada mesmo se você modificar durante a iteração sobre ela. então, para seguir o código, ele imprimirá 1's infinitamente.
for i in list: list.append(1) print 1
Então, usando list e dict colaborativamente, você pode resolver esse problema.
d_list=[] d_dict = {} for k in d_list: if d_dict[k] is not -1: d_dict[f(k)] = -1 # rather than deleting it mark it with -1 or other value to specify that it will be not considered further(deleted) d_dict[g(k)] = v # add a new item d_list.append(g(k))
fonte
Python 3, você deve apenas:
prefix = 'item_' t = {'f1': 'ffw', 'f2': 'fca'} t2 = dict() for k,v in t.items(): t2[k] = prefix + v
ou use:
Você nunca deve modificar o dicionário original, isso leva à confusão, bem como a possíveis bugs ou RunTimeErrors. A menos que você apenas anexe ao dicionário novos nomes de chave.
fonte
Hoje eu tive um caso de uso semelhante, mas em vez de simplesmente materializar as chaves no dicionário no início do loop, eu queria que as alterações no dict afetassem a iteração do dict, que era um dict ordenado.
Acabei construindo a seguinte rotina, que também pode ser encontrada no jaraco.itertools :
def _mutable_iter(dict): """ Iterate over items in the dict, yielding the first one, but allowing it to be mutated during the process. >>> d = dict(a=1) >>> it = _mutable_iter(d) >>> next(it) ('a', 1) >>> d {} >>> d.update(b=2) >>> list(it) [('b', 2)] """ while dict: prev_key = next(iter(dict)) yield prev_key, dict.pop(prev_key)
A docstring ilustra o uso. Esta função pode ser usada no lugar das
d.iteritems()
acima para obter o efeito desejado.fonte