【问题标题】:What is the meaning of list[:] in this code? [duplicate]这段代码中 list[:] 的含义是什么? [复制]
【发布时间】:2017-11-21 20:39:38
【问题描述】:

此代码来自 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)

为什么这段代码创建了一个无限循环而第一个没有?

【问题讨论】:

  • 因为,您在每次迭代时都在 words 列表中插入一个元素 -))
  • 第一个是最初的words而不是words本身的副本
  • 首先,您在开始向其中添加内容之前遍历words 的副本。在第二个中,您尝试循环遍历 words 并同时使 words 更长,因此您永远不会到达终点。
  • 该示例遍历数组的副本,而您的示例遍历列表本身。制作副本时,不会插入新单词,因此它只会遍历 3 个值。
  • 你总是可以在那里添加更好的答案@cᴏʟᴅsᴘᴇᴇᴅ。 FWIW,那里的简短回答确实解释了这里问题背后的原因。

标签: python list for-loop iteration


【解决方案1】:

这是陷阱之一! 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)

这基本上和你的第一个代码 sn-p 做同样的事情,并且更具可读性。

或者(正如 DSM 在 cmets 中提到的那样)和在 python >=3 上,您也可以使用 words.copy() 来做同样的事情。

【讨论】:

  • 当然是@Coldspeed - 你可以写成words[:] = [w for w in words if len(w) > 6][::-1] + words....
  • 乔恩,我确实提到了“更具可读性”,而不是更少......:P
  • @Coldspeed 更具可读性:words[:0] = [w for w in words if len(w) > 6].
  • 我建议强调使用 is 而不是 id() 来查看您是否有不同的对象,因为当对象超出范围并被释放时,id() 数字可以被重用,这尤其可能如果您正在使用交互式 Python 控制台。
  • list(words) 似乎和copy(words) 一样有效,并且不需要导入。
【解决方案2】:

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)

【讨论】:

    【解决方案3】:

    (除了@Coldspeed的回答)

    看下面的例子:

    words = ['cat', 'window', 'defenestrate']
    words2 = words
    words2 is words
    

    结果:True

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

    words = ['cat', 'window', 'defenestrate']
    words2 = words[:]
    words2 is words
    

    结果:False

    在这种情况下,我们已经创建了新对象。

    【讨论】:

      【解决方案4】:

      让我们看看迭代器和可迭代对象:

      可迭代对象是具有__iter__ 方法的对象,该方法返回一个 迭代器,或者它定义了一个__getitem__ 可以采用的方法 从零开始的顺序索引(并引发IndexError 时 索引不再有效)。所以一个可迭代对象是你 可以从中获取迭代器。

      迭代器是具有next (Python 2) 或__next__ (Python 3) 方法的对象。

      iter(iterable) 返回迭代器对象,list_obj[:] 返回一个新的列表对象,即 list_object 的精确副本。

      在您的第一种情况下:

      for w in words[:]
      

      for 循环将遍历列表的新副本,而不是原始单词。任何单词的变化对循环迭代都没有影响,循环正常终止。

      这就是循环的工作方式:

      1. 循环在可迭代对象上调用iter 方法并遍历迭代器

      2. 循环调用迭代器对象上的next 方法以从迭代器中获取下一项。重复此步骤,直到没有更多元素为止

      3. 在引发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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-09-09
        • 2019-10-02
        • 2017-02-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多