Jak uniknąć błędu „RuntimeError: słownik zmienił rozmiar podczas iteracji”?

258

Sprawdziłem wszystkie pozostałe pytania z tym samym błędem, ale nie znalazłem pomocnego rozwiązania = /

Mam słownik list:

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

w którym niektóre wartości są puste. Pod koniec tworzenia tych list chcę usunąć te puste listy przed zwróceniem mojego słownika. Obecnie próbuję to zrobić w następujący sposób:

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

daje mi to jednak błąd wykonania. Zdaję sobie sprawę, że nie można dodawać / usuwać elementów w słowniku podczas iteracji ... co w takim razie można by rozwiązać?

użytkownik1530318
źródło

Odpowiedzi:

454

W Python 2.x wywoływanie keystworzy kopię klucza, którą można iterować podczas modyfikowania dict:

for i in d.keys():

Zauważ, że to nie działa w Pythonie 3.x, ponieważ keyszwraca iterator zamiast listy.

Innym sposobem jest listwymuszenie wykonania kopii kluczy. Ten działa również w Pythonie 3.x:

for i in list(d):
Mark Byers
źródło
1
Wydaje mi się, że miałeś na myśli, że „dzwonienie keystworzy kopię kluczy , którą możesz iterować”, czyli pluralklucze, prawda? W przeciwnym razie, jak iterować po jednym kluczu?
Nawiasem mówiąc, nie wybieram
6
Lub krotkę zamiast listy, ponieważ jest szybsza.
Brambor
8
Aby wyjaśnić zachowanie Pythona 3.x, d.keys () zwraca iterowalny (nie iterator), co oznacza, że ​​jest to widok bezpośrednio na klucze słownika. Używanie for i in d.keys()faktycznie działa ogólnie w Pythonie 3.x , ale ponieważ iteruje się nad iterowalnym widokiem kluczy słownika, wywoływanie d.pop()podczas pętli prowadzi do tego samego błędu, jaki znaleziono. for i in list(d)emuluje nieco nieefektywne zachowanie Pythona 2 podczas kopiowania kluczy do listy przed iteracją, w szczególnych okolicznościach, takich jak twoje.
Michael Krebs,
twoje rozwiązanie python3 nie działa, gdy chcesz usunąć obiekt w wewnętrznym nagraniu. na przykład masz rysik A i dykta B w dykcie A. jeśli chcesz usunąć obiekt w dykcie B, pojawia się błąd
Ali-T
1
W python3.x list(d.keys())tworzy to samo wyjście, co list(d)wywołanie lista dictzwraca klucze. keysWezwanie (choć nie tak drogie) jest niepotrzebna.
Sean Breckenridge
46

Wystarczy użyć słownika, aby skopiować odpowiednie elementy do nowego nagrania

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

Do tego w Pythonie 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
źródło
11
d.iteritems()dał mi błąd. Użyłem d.items()zamiast tego - używając python3
wcyn
4
Działa to w przypadku problemu postawionego w pytaniu OP. Jednak każdy, kto przybył tutaj po trafieniu w RuntimeError w wielowątkowym kodzie, pamiętaj, że GIL CPython może zostać wydany również w środku listy i musisz to naprawić inaczej.
Yirkha
40

Musisz tylko użyć „kopiuj”:

W ten sposób iterujesz po oryginalnych polach słownika, a w locie możesz zmienić żądany dykt (d dykt). Działa na każdej wersji Pythona, więc jest bardziej przejrzysty.

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
źródło
Pracował! Dziękuję Ci.
Guilherme Carvalho Lithg
13

Starałbym się przede wszystkim unikać wstawiania pustych list, ale ogólnie używałbym:

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

Jeżeli przed 2.7:

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

Lub tylko:

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
źródło
+1: ostatnia opcja jest interesująca, ponieważ kopiuje tylko klucze tych elementów, które wymagają usunięcia. Może to dać lepszą wydajność, jeśli tylko niewielka liczba elementów wymaga usunięcia w stosunku do rozmiaru nagrania.
Mark Byers,
@ MarkByers tak - a jeśli jest ich duża liczba, lepszym rozwiązaniem jest ponowne powiązanie nagrania z nowym, filtrowanym filtrem. Zawsze oczekuje się tego, jak powinna działać konstrukcja
Jon Clements
4
Jednym z niebezpieczeństw związanych z ponownym wiązaniem jest to, że jeśli gdzieś w programie znajdował się obiekt, który zawierał odniesienie do starego dyktanda, nie widziałby zmian. Jeśli jesteś pewien, że tak nie jest, to na pewno ... jest to rozsądne podejście, ale ważne jest, aby zrozumieć, że to nie to samo, co modyfikowanie oryginalnego nagrania.
Mark Byers,
@ MarkByers bardzo dobry punkt - Ty i ja wiemy o tym (i niezliczonych innych), ale nie jest to oczywiste dla wszystkich. I położę pieniądze na stole, który cię też nie ugryzł z tyłu :)
Jon Clements
Bardzo ważne jest unikanie wstawiania pustych wpisów.
Magnus Bodin
12

W przypadku Python 3:

{k:v for k,v in d.items() if v}
ucyo
źródło
Ładne i zwięzłe. Pracował również dla mnie w Python 2.7.
donrondadon
6

Nie można iterować słownika, który zmienia się podczas pętli for. Wykonaj rzutowanie na listę i iteruj po tej liście, to działa dla mnie.

    for key in list(d):
        if not d[key]: 
            d.pop(key)
Alvaro Romero Diaz
źródło
1

W takich sytuacjach lubię robić głęboką kopię i przeglądać ją, modyfikując oryginalny dyktand.

Jeśli pole wyszukiwania znajduje się na liście, możesz wyliczyć w pętli for listy, a następnie określić pozycję jako indeks, aby uzyskać dostęp do pola w oryginalnym nagraniu.

Ajayi Oluwaseun Emmanuel
źródło
1

To działało dla mnie:

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

Przesłanie pozycji słownika na listę tworzy listę jej pozycji, dzięki czemu można iterować nad nią i unikać RuntimeError.

singrium
źródło
1

dictc = {"stName": "asas"} keys = dictc.keys () dla klucza na liście (klucze): dictc [key.upper ()] = 'Nowa wartość' print (str (dictc))

vaibhav.patil
źródło
0

Przyczyną błędu środowiska wykonawczego jest to, że nie można iterować przez strukturę danych, gdy jej struktura zmienia się podczas iteracji.

Jednym ze sposobów na osiągnięcie tego, czego szukasz, jest użycie listy, aby dołączyć klucze, które chcesz usunąć, a następnie użycie funkcji pop w słowniku, aby usunąć zidentyfikowany klucz podczas iteracji po liście.

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
źródło
0

Python 3 nie pozwala na usuwanie podczas iteracji (przy użyciu pętli powyżej) słownika. Istnieją różne alternatywy; Jednym prostym sposobem jest zmiana następującej linii

for i in x.keys():

Z

for i in list(x)
Hasham
źródło