Странное поведение Python с enumerate


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

Пример 1:

x = [1, 2, 3, 4, 5]
for i, s in enumerate(x):
    del x[0]
    print(i, s, x)

Пример 2:

x = [1,2,3,4,5]
for i, s in enumerate(x):
    x = [1]
    print(i, s, x)

Пример 1 выполняется только 3 раза, потому что когда i==3, len(x)==2.

Пример 2 выполняется 5 раз, хотя len(x)==1.

Итак, мой вопрос заключается в том, генерирует ли enumerate полный список пар (index, value) в начале цикла и повторяет ли он это? Или они генерируются на каждой итерации цикла?

6 18

6 ответов:

Перечислять() возвращает итератор или какой-либо другой объект, поддерживающий итерацию. Тот самый __следующий__() метод итератора, возвращаемого перечислять() возвращает кортеж, содержащий число (от начала, которое по умолчанию равно 0) и значения, полученные при итерации по iterable.

__следующий__() возвращает следующий элемент из контейнера. Если других элементов нет, вызовите исключение StopIteration.

Создает ли enumerate () полный список пар (индекс, значение)в начале цикла и повторяет его? Или они генерируются на каждой итерации цикла?

Таким образом, enumerate() возвращает итератор и на каждой итерации __next__() проверяет, есть ли другие элементы. enumerate() не создает полный список в начале цикла.

Как уже упоминалось, @Wisperwind, во втором случае вы присваиваете новый объект имени x. Объект, петля повторение не изменяется во время итерации.

В первом примере вы фактически изменяете список, который вы повторяете.

С другой стороны, во втором случае вы только присваиваете новому объекту имя x. Однако объект, по которому проходит цикл, не изменяется.

Взгляните на http://foobarnbaz.com/2012/07/08/understanding-python-variables/ для более подробного объяснения имен и переменных в Python.

Просто уточнение к тому, что сказали Васи Ахмад и Виспервинд. Оба утверждают, что "вы только присваиваете новый объект имени x". Это может быть немного запутанным, поскольку это может быть интерпретировано как " вы создаете новый объект ([1]) и сохраняете его в имени x, на что вы скажете: "Ну да, так почему же он не меняется?!"Чтобы увидеть, что происходит, распечатайте идентификатор объекта

x = [1, 2, 3, 4, 5]
y = x  # To keep a reference to the original list
print id(x), id(y)
for i, v in enumerate(x):
    x = [1]
    print id(x), id(y)
print id(x), id(y)


# output (somewhat contrived as I don't have a python environment set up)
#    X ID            Y ID
10000000000001 10000000000001
10000000000002 10000000000001
10000000000003 10000000000001
10000000000004 10000000000001
10000000000005 10000000000001
10000000000006 10000000000001
10000000000006 10000000000001

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

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

Действительно: ваш первый фрагмент изменяет повторяющийся список на месте; второй указывает переменную x на новый список, оставляя неизмененным список, пересеченный enumerate(). Вы можете увидеть это в действии, перейдя по следующим ссылкам www.pythontutor.com, которые позволяют вам сделать один шаг над вашим кодом и визуализировать содержимое ваших переменных:

Чтобы лучше видеть, что происходит, перейдите сюда вместо того, чтобы перейти через следующий расширенный код:

x = [1,2,3,4,5]
view = enumerate(x)
for i, s in view:
    x = [1]
    print(i, s, x)

Другие уже указывали, что ваш второй пример изменяет только значение, на которое указывает x, но не Список, над которым вы повторяете. Это идеальный пример для различия между обычным назначением (x = [1]) и назначением среза . (x[:] = [1]). Последний изменяет список x точек на на месте :

x = [1, 2, 3, 4, 5]
for i, s in enumerate(x):
    x[:] = [1]
    print(i, s, x)

Напечатает

(0, 1, [1])
x = [1, 2, 3, 4, 5]

Список [1, 2, 3, 4, 5] помечается как x

for i, s in enumerate(x):

Enumerate () присоединяет другой тег, поэтому [1, 2, 3, 4, 5] Теперь помечается x и y. enumerate () продолжит использовать тег y, а не тег x.

del x[0]

Список, хранящийся в памяти, изменен, поэтому x и y теперь оба ссылаются на [2, 3, 4, 5]

Альтернативно, когда вы используете

x = [1]

В памяти создается новый список [1], и тег x теперь указывает на это. Тег y по-прежнему указывает на первоначальный список.

Как работает переменная Python:
http://foobarnbaz.com/2012/07/08/understanding-python-variables/