Допустим, у нас есть словарь 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
.
Это хорошо определено? Не могли бы вы привести несколько ссылок в поддержку своего ответа?
(Довольно очевидно, как исправить это, если он сломан, так что это не тот угол, который мне нужен.)
python
dictionary
NPE
источник
источник
Ответы:
На странице документации Python (для Python 2.7 ) явно указано, что
Аналогично для Python 3 .
То же самое справедливо для
iter(d)
,d.iterkeys()
иd.itervalues()
, и я скажу, что это действительно такfor k, v in d.items():
(я не могу точно вспомнить, чтоfor
делает, но я не удивлюсь, если вызовет реализациюiter(d)
).источник
d.items()
должен быть безопасным в Python 2.7 (игра меняется с Python 3), поскольку он делает то, что по сути является копиейd
, поэтому вы не изменяете то, что повторяете.viewitems()
Алекс Мартелли весит на этом здесь .
Может быть небезопасно менять контейнер (например, dict) во время обхода контейнера. Так что
del d[f(k)]
может быть небезопасно. Как вы знаете, обходной путь заключается в использованииd.items()
(для перебора независимой копии контейнера) вместоd.iteritems()
(который использует тот же базовый контейнер).Можно изменить значение в существующем индексе словаря, но вставка значений в новые индексы (например
d[g(k)]=v
) может не работать.источник
Вы не можете этого сделать, по крайней мере, с помощью
d.iteritems()
. Я попробовал, и Python не справился сЕсли вы вместо этого используете
d.items()
, то работает.В Python 3
d.items()
это представление в словарь, какd.iteritems()
в Python 2. Для этого в Python 3 вместо этого используйтеd.copy().items()
. Это также позволит нам перебирать копию словаря, чтобы избежать изменения структуры данных, которую мы перебираем.источник
2to3
) Py2d.items()
на Py3 естьlist(d.items())
, хотяd.copy().items()
, вероятно, имеет сопоставимую эффективность.У меня есть большой словарь, содержащий массивы Numpy, поэтому вещь dict.copy (). 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, как ответы выше, но это дает практическое решение заявленной проблемы.
источник
Следующий код показывает, что это не совсем точно:
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'}
источник
{11: 'ax', 12: 'bx', 13: 'cx'}
но 21,22,23 должны дать вам представление о том, что на самом деле произошло: ваш цикл прошел через пункты 1, 2, 3, 11, 12, 13, но не смог подобрать второй раунд новых элементов по мере их вставки перед элементами, которые вы уже повторяли. Измените,h()
чтобы вернуться,x+5
и вы получите еще один x:'axxx'
и т. Д. Или «x + 3», и вы получите великолепное'axxxxx'
{11: 'ax', 12: 'bx', 13: 'cx'}
такими, как вы сказали, поэтому я обновлю свой пост об этом. В любом случае, это явно не определенное поведение.У меня та же проблема, и я использовал следующую процедуру для решения этой проблемы.
Список Python можно перебирать, даже если вы изменяете его во время итерации. поэтому для следующего кода он будет печатать 1 бесконечно.
for i in list: list.append(1) print 1
Таким образом, используя list и 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))
источник
Python 3 вам следует просто:
prefix = 'item_' t = {'f1': 'ffw', 'f2': 'fca'} t2 = dict() for k,v in t.items(): t2[k] = prefix + v
или используйте:
Вы никогда не должны изменять исходный словарь, это приведет к путанице, а также к возможным ошибкам или ошибкам RunTimeErrors. Если только вы не добавите в словарь новые имена ключей.
источник
Сегодня у меня был аналогичный вариант использования, но вместо того, чтобы просто материализовать ключи в словаре в начале цикла, я хотел, чтобы изменения в dict повлияли на итерацию dict, которая была упорядоченным 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()
указанной выше для достижения желаемого эффекта.источник