Цикл через список в Python и изменить его [дубликат]


этот вопрос уже есть ответ здесь:

  • В чем разница между list и list[:] в python? 6 ответов

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

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

4 56

4 ответа:

это один из подводных камней! из питона, который может избежать новичков.

The 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[:] возвращает a 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] называется нарезки оператора, и что он делает, это возвращает новую копию списка, начиная с index 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() который делает то же самое вещь.

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)

(в дополнение к @ Coldspeed ответ)

посмотрите на следующие примеры:

words = ['cat', 'window', 'defenestrate']
words2 = words
words2 is words

результаты: True

это означает, что имена word и words2 обратитесь к тому же объекту.

words = ['cat', 'window', 'defenestrate']
words2 = words[:]
words2 is words

результаты: False

в этом случае, мы создали новый объект.

давайте посмотрим на iterator и iterables:

iterable-это объект, который имеет __iter__ метод, который возвращает итератор, или который определяет __getitem__ метод, который может принимать последовательные индексы, начиная с нуля (и выдает IndexError когда индексы больше не действительны). Поэтому итератор-это объект, который вы может получить итератор от.

итератор-это объект с next (Python 2) или __next__ (Python 3) метод.

iter(iterable) возвращает объект итератора, и list_obj[:] возвращает новый объект списка, точную копию list_object.

в первом случае:

for w in words[:]

The for цикл будет перебирать новую копию списка, а не исходные слова. Любое изменение слов не влияет на итерацию цикла, и цикл завершается нормально.

это, как петли делает свою работу:

  1. петли называет iter метод метод и перебирает итератор

  2. петли называет next метод на объект итератора, чтобы получить следующий элемент из итератора. Этот шаг повторяется до тех пор, пока не останется больше элементов

  3. цикл завершается, когда a 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 возвращает соответственно. Вот почему ваш цикл работает бесконечно.

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