Подтвердить что ты не робот

Модификация 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.

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

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

4b9b3361

Ответ 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. Если только вы не добавляете в словарь новые имена ключей.