Модификация 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
.
Это хорошо определено? Не могли бы вы предоставить некоторые рекомендации для поддержки вашего ответа?
(Это довольно очевидно, как исправить это, если оно сломано, поэтому это не тот угол, который я получаю после.)
Ответы
Ответ 1
Он явно упоминается на странице документа Python (для Python 2.7), который
Использование iteritems()
при добавлении или удалении записей в словаре может вызвать RuntimeError
или не выполнить итерацию по всем элементам.
Аналогично для Python 3.
То же самое верно для iter(d)
, d.iterkeys()
и d.itervalues()
, и я пойду, насколько это возможно для for k, v in d.items():
(я не могу точно запомнить, что делает for
, но я не будет удивлен, если реализация называется iter(d)
).
Ответ 2
Алекс Мартелли весит здесь здесь.
Возможно, небезопасно изменять контейнер (например, dict) при циклическом перемещении по контейнеру.
Таким образом, del d[f(k)]
может быть небезопасным. Как вы знаете, обходным путем является использование d.items()
(для циклического преобразования независимой копии контейнера) вместо d.iteritems()
(который использует тот же базовый контейнер).
Можно изменить значение в существующем индексе dict, но вставка значений в новые индексы (например, d[g(k)]=v
) может не сработать.
Ответ 3
Вы не можете сделать это, по крайней мере, с помощью d.iteritems()
. Я попробовал это, и Python терпит неудачу с
RuntimeError: dictionary changed size during iteration
Если вы используете d.items()
, то он работает.
В Python 3, d.items()
- это вид словаря, например d.iteritems()
в Python 2. Для этого в Python 3 используйте d.copy().items()
. Это также позволит нам перебирать копию словаря, чтобы избежать изменения структуры данных, которую мы итерируем.
Ответ 4
Следующий код показывает, что это неверно определено:
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'}
Ответ 5
У меня есть большой словарь, содержащий массивы Numpy, поэтому вещь dict.copy(). keys(), предложенная @murgatroid99, была невозможна (хотя она и работала). Вместо этого я просто преобразовал key_view в список, и он отлично работал (в Python 3.4):
for item in list(dict_d.keys()):
temp = dict_d.pop(item)
dict_d['some_key'] = 1 # Some value
Я понимаю, что это не погрузится в философскую сферу внутренних процессов Python, как ответы выше, но это обеспечивает практическое решение заявленной проблемы.
Ответ 6
У меня такая же проблема, и я использовал следующую процедуру для решения этой проблемы.
Список Python может быть итерационным, даже если вы изменяете его во время итерации.
поэтому для следующего кода он будет печатать 1 бесконечно.
for i in list:
list.append(1)
print 1
Таким образом, используя список и dict совместно, вы можете решить эту проблему.
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))
Ответ 7
Сегодня у меня был похожий вариант использования, но вместо того, чтобы просто материализовать ключи в словаре в начале цикла, я хотел, чтобы изменения в dict влияли на его итерацию, которая была упорядоченной.
В итоге я создал следующую процедуру, которую также можно найти в jaraco.itertools:
def _mutable_iter(dict):
"""
Iterate over items in the dict, yielding the first one, but allowing
it to be mutated during the process.
>>> d = dict(a=1)
>>> it = _mutable_iter(d)
>>> next(it)
('a', 1)
>>> d
{}
>>> d.update(b=2)
>>> list(it)
[('b', 2)]
"""
while dict:
prev_key = next(iter(dict))
yield prev_key, dict.pop(prev_key)
Строка документа иллюстрирует использование. Эта функция может использоваться вместо d.iteritems()
выше, чтобы получить желаемый эффект.
Ответ 8
Python 3 вы должны просто:
prefix = 'item_'
t = {'f1': 'ffw', 'f2': 'fca'}
t2 = dict()
for k,v in t.items():
t2[k] = prefix + v
или используйте:
t2 = t1.copy()
Вы никогда не должны изменять оригинальный словарь, это приводит к путанице, а также к потенциальным ошибкам или RunTimeErrors. Если только вы не добавляете в словарь новые имена ключей.