Подтвердить что ты не робот

Что означает список [:] в этом коде?

Этот код из документации 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)

Почему этот код создает бесконечный цикл, а первый - нет?

4b9b3361

Ответ 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 соответственно. Вот почему ваш цикл работает бесконечно.

Подробнее об итерации и протоколе итерации вы можете посмотреть здесь.