此代码来自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)
为什么这段代码会创建一个无限循环而第一个没有呢?
答案 0 :(得分:83)
这是陷阱之一! 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)
,最终结果是您收到整个>的副本em> list。
如果您想让您的代码更具可读性,我推荐使用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()
执行相同的操作。
答案 1 :(得分:11)
words[:]
将words
中的所有元素复制到新列表中。因此,当您迭代words[:]
时,您实际上正在迭代words
当前拥有的所有元素。因此,当您修改words
时,这些修改的效果在words[:]
中不可见(因为您在开始修改words[:]
之前调用了words
)
在后一个示例中,您正在迭代words
,这意味着您对words
所做的任何更改确实对迭代器可见。因此,当您插入words
的索引0时,您将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)
答案 2 :(得分:5)
(除了@Coldspeed回答)
请看以下示例:
words = ['cat', 'window', 'defenestrate']
words2 = words
words2 is words
结果:True
这意味着名称word
和words2
指的是同一个对象。
words = ['cat', 'window', 'defenestrate']
words2 = words[:]
words2 is words
结果:False
在这种情况下,我们创建了新对象。
答案 3 :(得分:1)
让我们看看迭代器和迭代:
iterable是一个具有
__iter__
方法的对象,它返回一个 迭代器,或定义可以采用的__getitem__
方法 顺序索引从零开始(并在何时引发IndexError
索引不再有效)。因此,iterable是您的对象 可以从。获得迭代器。
迭代器是一个具有next
(Python 2)或__next__
(Python 3)方法的对象。
iter(iterable)
返回迭代器对象,list_obj[:]
返回一个新的列表对象,list_object的精确副本。
在你的第一个案例中:
for w in words[:]
for
循环将迭代列表的新副本而不是原始单词。单词的任何更改都不会对循环迭代产生影响,并且循环会正常终止。
这就是循环的工作方式:
在iterable上循环调用iter
方法并迭代迭代器
在迭代器对象上循环调用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
。这就是你的循环无限运行的原因。
有关迭代和迭代协议的更多信息,您可以查看here。