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

258

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

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

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

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

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

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

user1530318
источник

Ответы:

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: чрезвычайно хороший момент - мы с вами это знаем (и многие другие), но это не очевидно для всех. И я положу деньги на стол, он также не укусил тебя сзади :)
Джон Клементс
Точка избегания вставки пустых записей очень хорошая.
Магнус Бодин
12

Для Python 3:

{k:v for k,v in d.items() if v}
ucyo
источник
Красиво и лаконично. У меня тоже работал в Python 2.7.
Донрондадон
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.

singrium
источник
1

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

vaibhav.patil
источник
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)
Хашам
источник