Словарь бесконечного цикла неожиданно завершает работу

Я экспериментировал с различными способами создания бесконечного цикла в Python (кроме обычного, в while True), и пришел к этой идее:

x = {0: None}

for i in x:
    del x[i]
    x[i+1] = None  # Value doesn't matter, so I set it to None
    print(i)

На бумаге я проследил, как это будет бесконечно повторяться:

  1. Я перебираю значение ключа в словаре
  2. Я удаляю эту запись.
  3. Текущей позицией счетчика в цикле + 1 будет новый ключ со значением None который обновляет словарь.
  4. Я вывожу текущий счетчик.

Это, на мой взгляд, должно выводить натуральные числа в виде бесконечного цикла:

0
1
2
3
4
5
.
.
.

Я думал, что эта идея была умной, но когда я запускаю ее на Python 3.6, она выдает:

0
1
2
3
4

Да, это как-то остановилось после 5 итераций. Ясно, что в блоке кода цикла нет базового условия или значения часового значения, так почему же Python выполняет этот код только 5 раз?

Ответ 1

Нет никакой гарантии, что вы будете перебирать все свои записи в dict, если будете изменять его в цикле. Из документов:

Повторение представлений при добавлении или удалении записей в словаре может вызвать ошибку RuntimeError или не выполнить итерацию по всем записям.

Вы можете создать "перечислимый" бесконечный цикл, аналогичный вашей первоначальной попытке с использованием itertools.count(). Например:

from itertools import count

for i in count():
    print(i)
    # don't run this without some mechanism to break the loop, i.e.
    # if i == 10:
    #     break

# OUTPUT
# 0
# 1
# 2
# ...and so on

Ответ 2

В этом случае, как писал @benvc, это не гарантируется. Но если вам интересно, почему это работает в C-Python:

Реализация C-Python уничтожает объект dict после некоторых вставок и копирует его в новое пространство в памяти. Это не заботится об удалениях. Поэтому, когда это происходит, цикл замечает это и разрывается с исключением.

Перейдите по этой ссылке, если вы хотите узнать больше об этом, а также о многих других интересных внутренностях Python здесь.

https://github.com/satwikkansal/wtfpython#-modifying-a-dictionary-while-iterating-over-it

Ответ 3

Я только что проверил ваш код в python2 и python3

python3 output
0,1,2,3,4
python2
0,1,2,3,4,5,6,7

Одна вещь приходит на ум, что может происходить. Либо в вашем словаре выделяется только определенный объем памяти, когда вы создаете первое значение ключа, а когда вы удаляете значение ключа, мы не выделяем память или не освобождаем память, которую вы просто удаляете значение. Как только вся выделенная память используется, она выходит. Потому что если вы запустите без этого del, вы получите эту ошибку

RuntimeError: dictionary changed size during iteration

Таким образом, python создает достаточно памяти для этого значения ключа и еще несколько, и как только он используется, больше нет памяти, выделенной для вашего словаря.

Ответ 4

Как отмечали многие, изменение структуры данных во время итерации с помощью цикла for не очень хорошая идея. while цикл while допускает это, поскольку он переоценивает свое условие цикла на каждой итерации (я впечатлен, что никто еще не предложил это в качестве альтернативы). Нужно просто найти правильное условие цикла. Ваш сценарий должен стать:

x = {0: None}
while x:
    i, _ = x.popitem()
    print(i)
    # to avoid infinite loop while testing
    # if i == 10:
    #     break
    x[i+1] = None

В Python словарь ложный, когда он пустой (см. Документацию), поэтому цикл остановится, только если в начале итерации x пусто. Поскольку в словаре есть только одна пара ключ-значение, popitem() должно быть достаточно, чтобы получить эту пару и удалить ее из словаря. Поскольку следующее целое число добавляется сразу после опустошения словаря, условие цикла никогда не будет ложным при оценке, что приведет к бесконечному циклу.