Безопасно ли (документированное поведение?) Удалить домен итератора в исполнении

StackOverflow https://stackoverflow.com/questions/2965351

Вопрос

Я хотел знать, безопасно ли (задокументированное поведение?), Чтобы удалить пространство домена итератора в исполнении в Python.

Рассмотрим код:

import os
import sys

sampleSpace = [ x*x for x in range( 7 ) ]

print sampleSpace

for dx in sampleSpace:

    print str( dx )

    if dx == 1:

        del sampleSpace[ 1 ]
        del sampleSpace[ 3 ]

    elif dx == 25:

        del sampleSpace[ -1 ]

print sampleSpace

«Samplespace» - это то, что я называю «доменным пространством итератора» (если есть более подходящее слово/фразу, узнайте).

То, что я делаю, это удаление значений из этого, в то время как итератор «DX» проходит через него.

Вот что я ожидаю от кода:

Iteration versus element being pointed to (*):

0: [*0, 1, 4, 9, 16, 25, 36]
1: [0, *1, 4, 9, 16, 25, 36] ( delete 2nd and 5th element after this iteration )
2: [0, 4, *9, 25, 36]
3: [0, 4, 9, *25, 36] ( delete -1th element after this iteration )
4: [0, 4, 9, 25*] ( as the iterator points to nothing/end of list, the loop terminates )

.. и вот что я получаю:

[0, 1, 4, 9, 16, 25, 36]
0
1
9
25
[0, 4, 9, 25]

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

Следовательно - я хотел спросить вас, есть ли какое -то правило, как «итератор становится недействительным, если вы мутите его пространство во время итерации» в Python?

Безопасно ли (задокументированное поведение?) В Python делать такие вещи?

Это было полезно?

Решение

Из Учебник по Python:

Небезопасно изменять последовательность, которая итерации в цикле (это может произойти только для изменяющихся типов последовательностей, таких как списки). Если вам необходимо изменить список, который вы итерации (например, для дублирования выбранных элементов), вы должны использовать копию. Обозначение среза делает это особенно удобным:

>>> for x in a[:]: # make a slice copy of the entire list
...    if len(x) > 6: a.insert(0, x)
...
>>> a
['defenestrate', 'cat', 'window', 'defenestrate']

Другие советы

Вообще говоря, нет, это не безопасно, и вы можете получить непредсказуемое поведение. Итераторы не обязаны вести себя определенным образом при этих обстоятельствах.

Что происходит в вашем примере, так это

# list is [0, 1, 4, 9, 16, 25, 36]

if dx == 1:
    # we're at index 1 when this is true
    del sampleSpace[ 1 ]
    # we've removed the item at index 1, and the iterator will move to the next valid position - still index 1, but in a mutated list. We got lucky in this case
    # the list now contains [0, 4, 9, 16, 25, 36]
    del sampleSpace[ 3 ]   
    # we remove the item at index 3 which is (now) value 16
    # the list now contains [0, 4, 9, 25, 36]
elif dx == 25:

    del sampleSpace[ -1 ]
    # we remove the final item, list now looks like
    # the list now contains [0, 4, 9, 25]

Что вы имеете в виду под безопасным? Ваш код случается не для того, чтобы не поднять ошибки, но это явная возможность, конечно, подумайте об этом:

>>> a = range(3)
>>> for i in a:
    del a


Traceback (most recent call last):
  File "<pyshell#13>", line 2, in <module>
    del a
NameError: name 'a' is not defined
>>> a
[0, 1, 2]
>>> for i in a:
    del a[i+1]


Traceback (most recent call last):
  File "<pyshell#27>", line 2, in <module>
    del a[i+1]
IndexError: list assignment index out of range

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top