在Python中循环列表并修改它
这段代码来自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)
为什么这段代码会创build一个无限循环,而第一个代码不会呢?
这是一个难题! 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()
来执行相同的操作。
words[:]
将words
所有元素复制到新列表中。 所以当你迭代words[:]
,你实际上遍历了当前所有words
的元素。 所以当你修改words
,这些修改的效果在words[:]
是不可见的(因为在开始修改words
之前你调用了words[:]
)
在后面的例子中,您正在迭代words
,这意味着您对words
任何更改对您的迭代器words
都是可见的。 因此,当你插入到索引0的words
,你会用一个索引“碰撞”每个其他元素。 所以当你继续下一个迭代时,你会在下一个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)
(除了@速速答案)
看下面的例子:
words = ['cat', 'window', 'defenestrate'] words2 = words words2 is words
结果: True
这意味着名字和words2
是指同一个对象。
words = ['cat', 'window', 'defenestrate'] words2 = words[:] words2 is words
结果: False
在这种情况下,我们创build了新的对象。
我们来看看迭代器和迭代器:
iterable是一个对象,它有一个返回一个迭代器的
__iter__
方法,或者定义了一个可以从零开始的连续索引(并且在索引不再有效时引发IndexError
的__getitem__
方法。 所以一个迭代器是一个你可以从中获取迭代器的对象。
迭代器是具有next
(Python 2)或__next__
(Python 3)方法的对象。
iter(iterable)
返回迭代器对象,而list_obj[:]
返回一个新的列表对象,即list_object的精确副本。
在你的第一个案例中:
for w in words[:]
for
循环将迭代列表的新副本而不是原始单词。 任何词的改变对循环迭代都没有影响,循环正常结束。
循环是如何工作的:
-
循环在迭代器上调用
iter
方法并遍历迭代器 -
循环调用迭代器对象的
next
方法来获取来自迭代器的下一个项目。 重复此步骤,直到没有剩余元素 -
当引发
StopIteration
exception时,循环终止。
在你的第二种情况下:
words = ['cat', 'window', 'defenestrate'] for w in words: if len(w) > 6: words.insert(0, w) print(words)
您正在迭代原始列表中的单词,并将单词添加到单词对迭代器对象有直接影响。 所以每当你的单词更新时,相应的迭代器对象也被更新,因此创build一个无限循环。
看这个:
>>> 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
返回值。 这就是为什么你的循环无限运行。
有关更多的迭代和迭代协议,你可以看看这里 。