Удаление из списка, итерации по нему

Следующий код:

a = list(range(10))
remove = False
for b in a:
    if remove:
        a.remove(b)
    remove = not remove
print(a)

Выходы [0, 2, 3, 5, 6, 8, 9], а не [0, 2, 4, 6, 8]при использовании Python 3.2.

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

Обратите внимание, что я не ищу работу, но чтобы понять ее.

python,iterator,

10

Ответов: 4


14 ов принято

Я обсуждал это некоторое время, потому что подобные вопросы задавались много раз здесь. Но это просто уникально, чтобы получить преимущество от сомнений. (Тем не менее, я не буду возражать, если другие голосуют, чтобы закрыть.) Вот визуальное объяснение того, что происходит.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]       <-  b = 0; remove? no
 ^
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]       <-  b = 1; remove? yes
    ^
[0, 2, 3, 4, 5, 6, 7, 8, 9]          <-  b = 3; remove? no
       ^
[0, 2, 3, 4, 5, 6, 7, 8, 9]          <-  b = 4; remove? yes
          ^
[0, 2, 3, 5, 6, 7, 8, 9]             <-  b = 6; remove? no
             ^
[0, 2, 3, 5, 6, 7, 8, 9]             <-  b = 7; remove? yes
                ^
[0, 2, 3, 5, 6, 8, 9]                <-  b = 9; remove? no
                   ^

Поскольку никто другой не имеет, я попытаюсь ответить на ваши другие вопросы:

Почему ошибка не указана, чтобы указать, что базовый итератор изменяется?

Бросать ошибку без запрещая много совершенно допустимые конструкции петель, Python бы знать много о том, что происходит, и это, вероятно , придется получить эту информацию во время выполнения. Вся эта информация потребует времени для обработки. Это сделало бы Python намного медленнее, только в том месте, где скорость действительно рассчитывается - цикл.

Изменились ли механики из более ранних версий Python в отношении этого поведения?

Короче говоря, нет. Или, по крайней мере, я очень сомневаюсь в этом, и, конечно, он вел себя так, так как я изучил Python (2.4). Честно говоря, я бы ожидал, что любая простая реализация изменчивой последовательности будет вести себя именно таким образом. Любой, кто знает лучше, пожалуйста, поправьте меня. (На самом деле быстрый поиск документов подтверждает, что текст, который цитировал Микола , был в учебнике с версии 1.4 !)


4

Как объяснил Микола, фактический результат, который вы наблюдаете, вызван тем фактом, что удаление записи из списка смещает весь список на одно место, заставляя вас пропускать элементы.

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

  1. Диктоны сложны внутри, а списки - нет. Списки в основном представляют собой массивы. Дик должен обнаруживать, когда он изменен во время повторения, чтобы избежать сбоев при изменении внутренней структуры dict. Список может уйти, не выполняя эту проверку, потому что он просто убеждает, что его текущий индекс все еще находится в зоне действия.

  2. Исторически, (я не уверен сейчас), списки python были перезаписаны с помощью оператора []. Python будет оценивать список [0], список [1], список [2], пока не получит IndexError. В этом случае python не отслеживал размер списка до его начала, поэтому у него не было способа обнаружить, что размер списка был изменен.


3

Конечно, небезопасно изменять массив, когда вы выполняете итерацию по нему. Спектр говорит, что это плохая идея, и поведение не определено:

http://docs.python.org/tutorial/controlflow.html#for-statements

Итак, следующий вопрос: что именно происходит здесь под капотом? Если бы я должен был догадаться, я бы сказал, что он делает что-то вроде этого:

for(int i=0; i<len(array); ++i)
{
   do_loop_body(i);
}

Если вы полагаете, что это действительно то, что происходит, то это полностью объясняет наблюдаемое поведение. Когда вы удаляете элемент на или до текущего указателя, вы перемещаете весь список на 1 влево. В первый раз вы удаляете 1 - как обычно - но теперь список сдвигается назад. Следующая итерация вместо удара 2, вы нажмете 3. Затем вы удалите 4, и список сдвинется назад. Следующая итерация 7 и т. Д.


0

На вашей первой итерации вы не удаляетесь, и все денди.

Вторая итерация находится в позиции [1] последовательности, и вы удаляете «1». Итератор затем перенесет вас в позицию [2] в последовательности, которая теперь «3», поэтому «2» пропускается (поскольку «2» теперь находится в позиции [1] из-за удаления). Конечно, «3» не удаляется, поэтому вы переходите к позиции [3] в последовательности, которая теперь «4». Это удаляется, вы попадаете на позицию [5], которая теперь «6», и так далее.

Тот факт, что вы удаляете вещи, означает, что при каждом удалении позиция пропускается.

питон, итератор,
Похожие вопросы