Что означает список [:] в этом коде?
Этот код из документации Python. Я немного смущен.
words = ['cat', 'window', 'defenestrate']
for w in words[:]:
if len(w) > 6:
words.insert(0, w)
print(words)
И вот что я подумал сначала:
words = ['cat', 'window', 'defenestrate']
for w in words:
if len(w) > 6:
words.insert(0, w)
print(words)
Почему этот код создает бесконечный цикл, а первый - нет?
Ответы
Ответ 1
Это одна из ошибок! Python, который может избежать новичков.
words[:]
это волшебный соус здесь.
Заметим:
>>> words = ['cat', 'window', 'defenestrate']
>>> words2 = words[:]
>>> words2.insert(0, 'hello')
>>> words2
['hello', 'cat', 'window', 'defenestrate']
>>> words
['cat', 'window', 'defenestrate']
А теперь без [:]
:
>>> words = ['cat', 'window', 'defenestrate']
>>> words2 = words
>>> words2.insert(0, 'hello')
>>> words2
['hello', 'cat', 'window', 'defenestrate']
>>> words
['hello', 'cat', 'window', 'defenestrate']
Здесь главное отметить, что words[:]
возвращает copy
существующего списка, поэтому вы перебираете копию, которая не изменяется.
Вы можете проверить, ссылаетесь ли вы на одни и те же списки, используя id()
:
В первом случае:
>>> words2 = words[:]
>>> id(words2)
4360026736
>>> id(words)
4360188992
>>> words2 is words
False
Во втором случае:
>>> id(words2)
4360188992
>>> id(words)
4360188992
>>> words2 is words
True
Стоит отметить, что [i:j]
называется оператором среза, и он возвращает свежую копию списка, начиная с индекса i
до (но не включая) индекса j
.
Итак, words[0:2]
дают вам
>>> words[0:2]
['hello', 'cat']
Пропуск начального индекса означает, что он по умолчанию равен 0
, а пропуск последнего индекса означает, что по умолчанию он равен len(words)
, и в результате вы получаете копию всего списка.
Если вы хотите сделать свой код немного более читабельным, я рекомендую модуль copy
.
from copy import copy
words = ['cat', 'window', 'defenestrate']
for w in copy(words):
if len(w) > 6:
words.insert(0, w)
print(words)
Это в основном то же самое, что и ваш первый фрагмент кода, и гораздо более читабельно.
В качестве альтернативы (как упомянуто DSM в комментариях) и в python> = 3, вы также можете использовать words.copy()
который делает то же самое.
Ответ 2
words[:]
копирует все элементы в words
в новый список. Поэтому, когда вы переходите через words[:]
, вы на самом деле выполняете итерацию по всем элементам, которые в настоящее время есть words
. Поэтому, когда вы изменяете words
, эффекты этих изменений не отображаются в words[:]
(потому что вы вызывали words[:]
, прежде чем начинать изменять words
)
В последнем примере вы повторяете words
, что означает, что любые изменения, внесенные вами в words
, действительно видны вашему итератору. В результате, когда вы вставляете в индекс 0 из words
, вы "подбрасываете" каждый другой элемент в words
одним индексом. Поэтому, когда вы переходите к следующей итерации вашего цикла for, вы получите элемент в следующем индексе words
, но это только тот элемент, который вы только что видели (поскольку вы вставили элемент в начале список, перемещая все остальные элементы вверх по индексу).
Чтобы увидеть это в действии, попробуйте следующий код:
words = ['cat', 'window', 'defenestrate']
for w in words:
print("The list is:", words)
print("I am looking at this word:", w)
if len(w) > 6:
print("inserting", w)
words.insert(0, w)
print("the list now looks like this:", words)
print(words)
Ответ 3
(В дополнение к ответу @Coldspeed)
Посмотрите на приведенные ниже примеры:
words = ['cat', 'window', 'defenestrate']
words2 = words
words2 is words
Результаты: True
Это означает, что имена word
и words2
относятся к одному и тому же объекту.
words = ['cat', 'window', 'defenestrate']
words2 = words[:]
words2 is words
Результаты: False
В этом случае мы создали новый объект.
Ответ 4
Посмотрим на итератор и итерации:
Итерируемый объект, который имеет метод __iter__
, который возвращает итератором или который определяет метод __getitem__
, который может принимать последовательные индексы, начиная с нуля (и поднимает a IndexError
, когда индексы больше не действительны). Таким образом, итерируемый объект, который вы может получить итератор из.
Итератор - это объект с методом next
(Python 2) или __next__
(Python 3).
iter(iterable)
возвращает объект итератора, а list_obj[:]
возвращает новый объект списка, точную копию list_object.
В вашем первом случае:
for w in words[:]
Цикл for
будет перебирать новую копию списка, а не исходные слова. Любое изменение слов не влияет на итерацию цикла, и цикл обычно заканчивается.
Так работает цикл:
-
цикл вызывает метод iter
для итерации и итерации по итератору
-
loop вызывает метод next
на объекте итератора для получения следующего элемента из итератора. Этот шаг повторяется, пока осталось больше элементов
Цикл -
завершается, когда возникает исключение StopIteration
.
В вашем втором случае:
words = ['cat', 'window', 'defenestrate']
for w in words:
if len(w) > 6:
words.insert(0, w)
print(words)
Вы повторяете исходные слова списка, и добавление элементов к словам оказывает прямое влияние на объект итератора. Поэтому каждый раз, когда ваши слова обновляются, соответствующий объект итератора также обновляется и, следовательно, создает бесконечный цикл.
Посмотрите на это:
>>> l = [2, 4, 6, 8]
>>> i = iter(l) # returns list_iterator object which has next method
>>> next(i)
2
>>> next(i)
4
>>> l.insert(2, 'A')
>>> next(i)
'A'
Каждый раз, когда вы обновляете исходный список до StopIteration
, вы получите обновленный итератор и next
соответственно. Вот почему ваш цикл работает бесконечно.
Подробнее об итерации и протоколе итерации вы можете посмотреть здесь.