Изменение Python dict во время итерации по нему


Допустим, у нас есть словарь Python d, и мы повторяем это так:

for k,v in d.iteritems():
    del d[f(k)] # remove some item
    d[g(k)] = v # add a new item

(f и g это просто некоторые преобразования черного ящика.)

другими словами, мы пытаемся добавить / удалить элементы в d при итерации по нему с помощью iteritems.

это хорошо определена? Не могли бы вы предоставить некоторые ссылки для поддержки Вашего ответа?

(это довольно очевидно, как исправить это, если он сломан, так что это не угол я после.)

6 64

6 ответов:

это явно упоминается на странице документа Python (для Python 2.7), что

используя iteritems() при добавлении или удалении записей в словаре может возникнуть RuntimeError или не удается выполнить итерацию по всем записям.

аналогично Python 3.

то же самое относится и к iter(d),d.iterkeys() и d.itervalues(), и я дойду до того, что скажу, что это делает для for k, v in d.items(): (Я не могу точно вспомнить, что for делает, но Я не удивлюсь, если реализация называется iter(d)).

Алекс Мартелли взвешивает это здесь.

это может быть небезопасно, чтобы изменить контейнер (например, dict) во время цикла над контейнером. Так что del d[f(k)] может быть небезопасно. Как вы знаете, обходной путь заключается в использовании d.items() (перебирать независимую копию контейнера) вместо d.iteritems() (который использует тот же самый основной контейнер).

это нормально, чтобы изменить значение в элементе существующей индекс dict, но вставка значений в новые индексы (например,d[g(k)]=v) могут не работать.

вы не можете этого сделать, по крайней мере с d.iteritems(). Я попробовал, и Python терпит неудачу с

RuntimeError: dictionary changed size during iteration

если вы используете d.items(), то он работает.

В Python 3, d.items() это вид в словарь, как d.iteritems() в Python 2. Чтобы сделать это в Python 3, вместо этого используйте d.copy().items(). Это также позволит нам перебирать копию словаря, чтобы избежать изменения структуры данных, которую мы перебираем.

следующий код показывает, что это не совсем точно определено:

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

первый пример вызывает g (k) и выдает исключение (словарь изменил размер во время итерации).

второй пример вызывает h( k) и не вызывает исключения, но выводит:

{21: 'axx', 22: 'bxx', 23: 'cxx'}

, который, глядя на код, кажется неправильным - я бы ожидал что-то вроде:

{11: 'ax', 12: 'bx', 13: 'cx'}

У меня есть большой словарь, содержащий массивы numpy, поэтому дикт.копия.)(keys () вещь, предложенная @murgatroid99, была невыполнима (хотя и работала). Вместо этого я просто преобразовал keys_view в список, и он отлично работал (в Python 3.4):

for item in list(dict_d.keys()):
    temp = dict_d.pop(item)
    dict_d['some_key'] = 1  # Some value

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

Я получил ту же проблему, и я использовал следующую процедуру, чтобы решить эту проблему.

список Python может быть повторен, даже если вы изменяете его во время итерации. поэтому для следующего кода он будет печатать 1 бесконечно.

for i in list:
   list.append(1)
   print 1

таким образом, используя список и дикт совместно можно решить эту проблему.

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))