Цикл через список в 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
возвращает соответственно. Вот почему ваш цикл работает бесконечно.для получения дополнительной информации об итерации и протоколе итерации вы можете посмотреть здесь.