这段代码中list [:]的含义是什么?

时间:2017-06-19 14:53:51

标签: python list for-loop iteration

此代码来自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 个答案:

答案 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

这意味着名称wordwords2指的是同一个对象。

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循环将迭代列表的新副本而不是原始单词。单词的任何更改都不会对循环迭代产生影响,并且循环会正常终止。

这就是循环的工作方式:

  1. 在iterable上循环调用iter方法并迭代迭代器

  2. 在迭代器对象上循环调用next方法以从迭代器获取下一个项目。重复此步骤,直到不再有元素

  3. 循环在引发StopIteration异常时终止。

  4. 在你的第二个案例中:

    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