迭代器在列表推导中使用时缺少的元素

时间:2015-05-08 17:12:31

标签: python python-2.7 iterator list-comprehension

以下示例显示了不同的行为,具体取决于列表推导中最右边的生成器是列表还是迭代器。具体来说,使用迭代器时生成的结果较少 - 我发现这种行为非常令人惊讶。

我是Python的新手,所以我想我错过了一些明显的东西,但我很感激解释。

>>> import itertools
>>> xs = range(0, 5)
>>> ys = range(0, 3)
>>> zs_l = [x for x in xs if not x in ys]
>>> zs_l
[3, 4]

# Validate the contents of the iterator, and create it again
>>> zs_i = itertools.ifilterfalse(lambda x: x in ys, xs)
>>> list(zs_i)
[3, 4]
>>> list(zs_i)
[]
>>> zs_i = itertools.ifilterfalse(lambda x: x in ys, xs)

>>> [(i,z) for i in [1,2] for z in zs_l]
[(1, 3), (1, 4), (2, 3), (2, 4)]
>>> [(i,z) for i in [1,2] for z in zs_i]
[(1, 3), (1, 4)]

3 个答案:

答案 0 :(得分:4)

itertools.ifilterfalse是一个生成器。如果你通过调用yield来消费它list的所有内容,那么它就不会产生任何结果。

[(i,z) for i in [1,2] for z in zs_i]

zs_id已用尽i = 1。当i = 2时,zs_i不会产生任何结果。

答案 1 :(得分:2)

引用itertools.ifilterfalse个文档,

  

制作迭代器,过滤元素......

引用python的术语iterator

的文档
  

表示数据流的对象。重复调用迭代器的next()方法返回流中的连续项。 当没有更多数据可用时,会引发StopIteration异常。此时,迭代器对象已耗尽,并且对其next()方法的任何进一步调用再次引发StopIteration迭代器需要具有返回迭代器的__iter__()方法对象本身因此每个迭代器也是可迭代的,并且可以在接受其他迭代的大多数地方使用。一个值得注意的例外是尝试多次迭代传递的代码。 容器对象(例如list)每次将其传递给iter()函数或在for循环中使用它时都会生成一个全新的迭代器。使用迭代器尝试此操作只会返回上一次迭代过程中使用的相同耗尽迭代器对象,使其看起来像一个空容器。

上面的粗体文字回答了你的问题。

当你这样做时

>>> [(i,z) for i in [1,2] for z in zs_i]
[(1, 3), (1, 4)]

迭代器zs_i在第一次迭代中用for循环耗尽。因此,当它再次用于for循环时,第二次,如上面的文档所示,StopIteration被引发。因此,对于循环中断并且它不再处理它。

但是同样适用于range返回的列表,因为从上面的文档中可以看出

  

每次将容器对象(例如list)传递给iter()函数或在for循环中使用它时,它都会产生一个全新的迭代器。

因此,当您在每次迭代中将列表传递给for循环时,它会创建一个新的迭代器,这就是它按预期工作的原因。

答案 2 :(得分:1)

这个答案是对其他答案的补充,这些答案更详细地解释了潜在的机制。如果你想要这个,那么必须在理解中多次重建生成器。

一种方法是为嵌套for循环的每次传递初始化一个新生成器:

>>> [(i,z) for i in [1,2] for z in itertools.ifilterfalse(lambda x: x in ys, xs)]
[(1, 3), (1, 4), (2, 3), (2, 4)]