Как избежать ошибки «RuntimeError: словарь изменил размер во время итерации»?


258

Я проверил все остальные вопросы с той же ошибкой, но не нашел полезного решения = /

У меня есть словарь списков:

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

в котором некоторые значения пусты. В конце создания этих списков я хочу удалить эти пустые списки перед возвратом моего словаря. Тока я пытаюсь сделать это следующим образом:

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

однако, это дает мне ошибку во время выполнения. Я знаю, что вы не можете добавлять / удалять элементы в словаре, перебирая его ... что тогда можно обойти?

Ответы:


455

В 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 не работает, когда вы хотите удалить объект во внутреннем dict. например, у вас есть dict A и dict B в dict A. Если вы хотите удалить объект в dict B, возникает ошибка
Ali-T

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
Это работает для проблемы, поставленной в вопросе OP. Однако любой, кто пришел сюда после нажатия этой RuntimeError в многопоточном коде, должен знать, что GIL CPython может быть выпущен и в середине понимания списка, и вы должны исправить это по-другому.
Йирха

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: последний вариант интересен тем, что копирует только ключи тех элементов, которые нужно удалить. Это может дать лучшую производительность, если только небольшое количество элементов нужно удалить относительно размера dict.
Марк Байерс

@MarkByers, да - и если большое количество делает, то лучше снова связать dict с новым, который фильтруется, - лучший вариант. Это всегда ожидание того, как структура должна работать
Джон Клементс

4
Одна из опасностей повторного связывания состоит в том, что если где-то в программе был объект, который содержал ссылку на старый запрос, он не увидел бы изменений. Если вы уверены, что это не так, тогда конечно ... это разумный подход, но важно понимать, что это не совсем то же самое, что модифицировать исходный диктат.
Марк Байерс

@MarkByers: чрезвычайно хороший момент - мы с вами это знаем (и многие другие), но это не очевидно для всех. И я положу деньги на стол, он также не укусил тебя сзади :)
Джон Клементс

Точка избегания вставки пустых записей очень хорошая.
Магнус Бодин


6

Вы не можете выполнять итерацию по словарю во время его изменения во время цикла for. Сделайте приведение в список и переберите этот список, он мне подходит.

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

1

В подобных ситуациях я хотел бы сделать глубокую копию и пройтись по ней, изменяя исходный текст.

Если поле поиска находится в списке, вы можете перечислить его в цикле for списка, а затем указать позицию в качестве индекса для доступа к полю в исходном dict.


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"} keys = dictc.keys () для ключа в списке (ключи): dictc [key.upper ()] = 'Новое значение' print (str (dictc))


0

Причина ошибки во время выполнения состоит в том, что вы не можете выполнять итерацию по структуре данных, пока ее структура изменяется во время итерации.

Одним из способов достижения того, что вы ищете, является использование списка для добавления ключей, которые вы хотите удалить, а затем использование функции pop в словаре для удаления идентифицированного ключа во время итерации по списку.

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.