Цикл через список в 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 ответа:
это один из подводных камней! из питона, который может избежать новичков.
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[:]возвращает acopyсуществующего списка, поэтому вы повторяете копию, которая не изменяется.вы можете проверить, ссылаетесь ли вы на те же списки с помощью
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]называется нарезки оператора, и что он делает, это возвращает новую копию списка, начиная с indexi, до (но не включая) индекс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действительно видны итератор. В результате, при вставке в индекс 0words, вы "поднять" каждый другой элемент в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цикл будет перебирать новую копию списка, а не исходные слова. Любое изменение слов не влияет на итерацию цикла, и цикл завершается нормально.это, как петли делает свою работу:
петли называет
iterметод метод и перебирает итератор
петли называет
nextметод на объект итератора, чтобы получить следующий элемент из итератора. Этот шаг повторяется до тех пор, пока не останется больше элементов
цикл завершается, когда 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возвращает соответственно. Вот почему ваш цикл работает бесконечно.для получения дополнительной информации об итерации и протоколе итерации вы можете посмотреть здесь.