Изменить список и словарь во время итерации, почему он не работает на 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)
как это приведет к созданию списка из ключей словаря, который не изменится во время итерации