Модификация слова Python во время его итерации - PullRequest
69 голосов
/ 21 июля 2011

Допустим, у нас есть словарь 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.

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

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

Ответы [ 8 ]

45 голосов
/ 21 июля 2011

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

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

Аналогично для Python 3 .

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

43 голосов
/ 21 июля 2011

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

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

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

22 голосов
/ 21 июля 2011

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

RuntimeError: dictionary changed size during iteration

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

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

6 голосов
/ 22 января 2015

У меня есть большой словарь, содержащий массивы 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, как ответы выше, но оно обеспечивает практическое решение поставленной проблемы.

6 голосов
/ 21 июля 2011

Следующий код показывает, что это не очень хорошо определено:

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'}
1 голос
/ 19 июня 2014

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

Список 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))
0 голосов
/ 05 февраля 2019

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

0 голосов
/ 09 января 2019

Сегодня у меня был похожий вариант использования, но вместо того, чтобы просто материализовать ключи в словаре в начале цикла, я хотел, чтобы изменения в dict влияли на итерацию dict, которая была упорядоченной. 1001 *

Я закончил сборку следующей процедуры, которую также можно найти в 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() выше, чтобы получить желаемый эффект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...