Изменить список и словарь во время итерации, почему он не работает на dict?

Давайте рассмотрим этот код, который выполняет итерацию по списку при удалении элемента на каждой итерации:

x = list(range(5))

for i in x:
    print(i)
    x.pop()

Он будет печатать 0, 1, 2. Только первые три элемента печатаются, поскольку последние два элемента в списке были удалены с помощью первых двух итераций.

Но если вы попробуете что-то подобное на дикторе:

y = {i: i for i in range(5)}

for i in y:
    print(i)
    y.pop(i)

Он будет печатать 0, а затем поднять RuntimeError: dictionary changed size during iteration, потому что мы удаляем ключ из словаря, итерации по нему.

Конечно, изменение списка во время итерации плохое. Но почему RuntimeError не RuntimeError как в случае с словарем? Есть ли веские основания для такого поведения?

Ответ 1

Я думаю, что причина проста. list упорядочен, dict (до Python 3.6/3.7), а set - нет. Поэтому изменение list когда вы повторяете, не рекомендуется в качестве лучшей практики, но оно ведет к последовательному, воспроизводимому и гарантированному поведению.

Вы можете использовать это, например, пусть говорят, что вы хотите разбить list с четным числом элементов пополам и обратить вспять вторую половину:

>>> lst = [0,1,2,3]
>>> lst2 = [lst.pop() for _ in lst]
>>> lst, lst2
([0, 1], [3, 2])

Конечно, есть намного лучшие и более интуитивные способы выполнения этой операции, но дело в том, что это работает.

Напротив, поведение для dict и set является полностью специфичным для реализации, поскольку порядок итераций может меняться в зависимости от хэширования.

Вы получаете RunTimeError с collections.OrderedDict, предположительно для согласованности с поведением dict. Я не думаю, что какое-либо изменение в dict поведении, вероятно, после Python 3.6 (где dict гарантированно поддерживается вставка заказано), так как он нарушит обратную совместимость для реальных случаев использования.

Обратите внимание, что collections.deque также вызывает RuntimeError в этом случае, несмотря на то, что он упорядочен.

Ответ 2

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

И эта проблема не d.keys() путем итерации d.keys() вместо d, поскольку в Python 3, d.keys() возвращает динамическое представление ключей в dict что приводит к той же проблеме. Вместо этого перебираем list(d) как это приведет к созданию списка из ключей словаря, который не изменится во время итерации