Странное поведение Python с перечислением
Я знаю, что я не должен изменять список внутри цикла, но просто из любопытства, я хотел бы знать, почему количество итераций отличается от двух следующих примеров.
Пример 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)
в начале цикла и перебирает его? Или они генерируются на каждой итерации цикла?
Ответы
Ответ 1
enumerate() возвращает итератор или какой-либо другой объект, который поддерживает итерацию. __ next __() метод итератора, возвращенный enumerate() возвращает кортеж, содержащий счетчик (от начала, который по умолчанию равен 0), и значения, полученные от итерации по итерабельному.
__ next __() возвращает следующий элемент из контейнера. Если дополнительных элементов нет, поднимите исключение StopIteration
.
Перечисляет ли enumerate() полный список пар (индекс, значение) в начале цикла и итерации через него? Или они генерируются на каждой итерации цикла?
Итак, enumerate()
возвращает итератор и на каждой итерации __next__()
проверяет, есть ли дополнительные элементы. enumerate()
не создает полный список в начале цикла.
Как упоминалось в @Wisperwind, во втором случае вы назначаете новый объект имени x
. Объект, итерация цикла не изменяется во время итерации.
Ответ 2
В первом примере вы фактически изменяете список, который вы повторяете.
С другой стороны, во втором случае вы назначаете новый объект только для имени x
. Объект, который цикл выполняет итерации, не изменяется, однако.
Посмотрите http://foobarnbaz.com/2012/07/08/understanding-python-variables/ для более подробного объяснения имен и переменных в Python.
Ответ 3
Просто разъяснение тому, что сказал Васи Ахмад и Виспервинд. Оба утверждают, что "вы назначаете новый объект только для имени 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
of x
меняется каждый раз через цикл, и когда вы закончите цикл, x
укажет на последнюю модификацию, выполненную в цикле. Когда вы проходите через свой цикл, он выполняет итерацию по исходному экземпляру x, независимо от того, можете ли вы все еще ссылаться на него.
Как вы можете видеть, y
указывает на оригинал x
. Когда вы выполняете свои итерации через цикл, даже если x
изменяется, y
все еще указывает на исходный x
, который все еще зацикливается.
Ответ 4
В самом деле: ваш первый фрагмент изменяет список повторений на месте; второй указывает переменную 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)
Ответ 5
Другие уже указали, что ваш второй пример изменяет значение, на которое 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])
Ответ 6
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/