迭代器是否在Python中保留自己的数组副本?

时间:2018-11-04 17:10:05

标签: python

我这里有一个简单的程序,可以使用reversed()返回的迭代器删除列表中的元素。

代码如下:

def removeElement( nums, val ):
  for x in reversed(nums):
    if x == val:
      nums.remove(x)

如果我要使用for x in nums:遍历列表,那么当一个元素(也是当前元素)被删除时,我们最终将跳过数组中的元素。但是,在使用reversed()返回的迭代器的情况下不会发生这种情况,对remove()的调用似乎对反向列表没有影响。

我还注意到两个列表中的每个元素都具有相同的内存地址,因此reversed()不会分配一个全新的列表。

尽管调用reversed()会影响原始列表,但remove()返回的迭代器如何保留列表的原始结构?

2 个答案:

答案 0 :(得分:4)

  

迭代器是否在Python中保留自己的数组副本?

,或者至少在CPython中不是。我们可以对列表进行简单的测试:

>>> l = [1,2]
>>> for x in reversed(l):
...     l[0] = 3
...     print(x)
... 
2
3

在这里,我们看到我们的编辑(l[0] = 3)对产生什么​​元素有影响。最后,我们看到3。如果reversed(..)首先构造了一个副本,则它将发出1。对于只有一个元素的列表,这是行不通的,因为在这种情况下,我们将x设置为1 之前进入循环的正文,然后更改列表,将对该元素没有影响。

一个列表定义了一个__reversed__函数,这意味着在进行reversed(..)调用的情况下,它将使用此函数枚举相反的元素。可以在Python中实现此功能,例如:

class list:

    # ...

    def __reversed__(self):
        for i in range(len(self)-1, -1, -1):
            if i >= len(self):
                yield self[i]
            else:
                break

在CPython中,它实际上是通过listreviterobject object实现的,还具有一个索引decremented when accessing the next item的索引it_index。我更新了Python版本,使其与CPython实现更加等效。

如果.remove()元素,则将删除Python找到的第一项,因此它可能会“移动”光标下的元素。结果,有可能我们多次计数同一项目。然而,这最终不会产生太大影响。例如:

1 4 4 2 4 4

如果我们删除4,我们将通过以下方式进行操作:

1 4 4 2 4 4   (start cursor at the right)
            ^

1 4 4 2 4 4   (start cursor at the right)
          ^

1 4 2 4 4     (remove 4)
          ^

1 4 2 4 4     (advance cursor)
        ^

1 2 4 4       (remove 4)
        ^

1 2 4 4       (advance cursor)
      ^

1 2 4         (remove 4)
      ^

1 2 4         (advance cursor)
    ^

1 2           (remove 4)
    ^

1 2           (advance cursor)
  ^

1 2           (advance cursor)
^

因此,鉴于光标位于我们要删除的元素上,我们将删除该元素的第一个匹配项。该位置始终位于左侧(或光标下方),因为如果光标位于该位置的左侧,则不能将其放置在该元素上。

如果删除了该元素,则可以说列表的一部分在“光标下方”移动。这意味着,由于光标向左移动,因此它相对地停留在同一位置。这将一直发生,直到删除最后一个元素。接下来,光标可以继续向左移动,并且永远不会再看到可以删除的元素(因为这些元素已经被删除)。

一个简单的程序可以产生经验证明其有效,如下所示:

while True:
    l = [randint(0, 9) for _ in range(10)]
    x = randint(0, 9)
    removeElement(l, x)
    assert x not in l

因此,我们在此处生成10个随机元素的列表,然后删除。最后,我们检查l不再包含x元素。当然,这不是 的基本证明,但如果运行了足够长的时间,它至少可以为我们提供一些证据,表明它适用于值在0之间的10个元素的列表。和9

但是,最好不要同时以任何方式修改和迭代集合。如果您以后想要更改代码以查找当前元素和下一个元素,则可能会出错。

答案 1 :(得分:1)

reversed()的官方文档说:

  

反向(seq)

     

返回一个反向迭代器。 seq必须是具有 reverse ()方法或支持序列协议( len ()方法和 getitem ()方法)的对象。整数参数从0开始)。

文档中似乎对该功能的工作方式没有任何进一步的保证。

给出该文档,我希望将reversed()植入到A=("a", "b", "c", "d)这样的列表中会返回A[3]A[2]A[1],最后是{{ 1}},我看不出给定实现会存储列表结构的原因。