Як уникнути помилки "RuntimeError: словник змінив розмір під час ітерації" помилку?


258

Я перевірив усі інші питання з тією ж помилкою, але не знайшов корисного рішення = /

У мене є словник списків:

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

в якому деякі значення порожні. Наприкінці створення цих списків я хочу видалити ці порожні списки перед поверненням свого словника. Поточний Я намагаюся зробити це наступним чином:

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

однак це дає мені помилку виконання. Я усвідомлюю, що ви не можете додавати / видаляти елементи зі словника під час ітерації через нього ... що б тоді було обходити це?

Відповіді:


454

У Python 2.x виклик keysробить копію ключа, який ви можете повторити, змінюючи dict:

for i in d.keys():

Зауважте, що це не працює в Python 3.x, оскільки keysповертає ітератор замість списку.

Інший спосіб - використовувати, listщоб змусити зробити копію ключів. Цей також працює в Python 3.x:

for i in list(d):

1
Я вважаю, ви мали на увазі, що "виклик keysробить копію ключів, яку ви можете повторити", так само pluralклавіші? Інакше як можна повторити один ключ? Я, до речі, не збираю ніт, мені щиро цікаво дізнатися, чи справді це ключ або ключі
HighOnMeat

6
Або кортеж замість списку, оскільки це швидше.
Брамбор

8
Щоб уточнити поведінку python 3.x, d.keys () повертає ітерабельний (не ітератор), що означає, що це перегляд клавіш словника безпосередньо. Використання for i in d.keys()насправді працює в python 3.x в цілому , але оскільки воно перебирає ітерабельний вигляд клавіш словника, виклик d.pop()під час циклу призводить до тієї ж помилки, що і ви знайшли. for i in list(d)емулює дещо неефективну поведінку python 2 щодо копіювання ключів до списку перед повторенням, для особливих обставин, як ваша.
Майкл Кребс

ваше рішення python3 не працює, коли ви хочете видалити об'єкт у внутрішньому диктаті. наприклад, у вас є дік А та диктат В у дікті А. Якщо ви хочете видалити об'єкт у дікті Б, виникає помилка
Алі-Т

1
У python3.x, list(d.keys())створює такий же висновок , як list(d), виклик listна через dictповернення ключів. keysВиклику (хоча і не так дорого) НЕ є необхідним.
Шон Брекенридж

46

Просто скористайтеся розумінням словника, щоб скопіювати відповідні пункти в новий дикт

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

Для цього в 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]}

11
d.iteritems()дав мені помилку. Я d.items()замість цього використовував - використовуючи python3
wcyn

4
Це працює для проблеми, поставленої у питанні ОП. Однак кожен, хто прийшов сюди після натискання цього RuntimeError у багатопотоковому коді, майте на увазі, що GIL CPython може бути звільнений і в середині списку розуміння списку, і вам доведеться виправити це по-іншому.
Yirkha

40

Вам потрібно використовувати лише "копію":

Таким чином, ви перейдете до оригінальних словникових полів і під час руху зможете змінити потрібний dict (d dict). Це робота над кожною версією python, тому це більш зрозуміло.

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

Працювали! Дякую.
Гільгерме Карвальо Літг

13

Я б намагався уникати в першу чергу порожніх списків, але, як правило, використовував:

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

Якщо до 2.7:

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

або просто:

empty_key_vals = list(k for k in k,v in d.iteritems() if v)
for k in empty_key_vals:
    del[k]

+1: останній варіант цікавий тим, що він копіює лише ключі тих елементів, які потрібно видалити. Це може забезпечити кращі показники, якщо лише невелика кількість предметів потребує видалення відносно розміру диктату.
Марк Байєрс

@MarkByers yup - і якщо велика кількість робить, то повторне прив’язування дикта до нового, який відфільтровано, є кращим варіантом. Це завжди очікування того, як повинна працювати структура
Джон Клементс

4
Одна небезпека, пов’язана з повторним перенаправленням, - якщо десь у програмі був об’єкт, який містив посилання на старий дикт, він не побачив би змін. Якщо ви впевнені, що це не так, то впевнені ... це розумний підхід, але важливо розуміти, що це не зовсім те саме, що зміна оригінального дикту.
Марк Байєрс

@MarkByers надзвичайно вдалий момент - Ви і я знаємо це (і незліченна кількість інших), але це не очевидно для всіх. І я покладу гроші на стіл, він також не покусав вас в тилу :)
Джон Клементс

Суть уникнення вставки порожніх записів дуже хороша.
Магнус Бодін


6

Ви не можете повторити словник під час зміни циклу для циклу. Зробіть кастинг до списку та повторіть цей список, він працює для мене.

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

1

У таких ситуаціях я люблю робити глибоку копію та прокручувати цю копію, змінюючи оригінальний дікт.

Якщо поле пошуку знаходиться в списку, ви можете перерахувати цикл for для списку, а потім вказати позицію як індекс, щоб отримати доступ до поля в вихідному диктаті.


1

Це працювало для мене:

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

Передаючи елементи словника до списку, створюється список його елементів, тож ви можете перебирати його та уникати RuntimeError.


1

dictc = {"stName": "asas"} ключі = dictc.keys () для ключа в списку (ключі): dictc [key.upper ()] = 'Нове значення' print (str (dictc))


0

Причина помилки під час виконання полягає в тому, що ви не можете перебирати структуру даних, тоді як її структура змінюється під час ітерації.

Одним із способів досягти того, що ви шукаєте, є використання списку для додавання ключів, які ви хочете видалити, а потім використання поп-функції зі словника для видалення ідентифікованого ключа під час ітерації через список.

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)

0

Python 3 не дозволяє видалити під час ітерації (використовуючи для циклу вище) словник. Існують різні альтернативи; один простий спосіб - змінити наступний рядок

for i in x.keys():

З

for i in list(x)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.