【问题标题】:How to get the n next values of a generator in a list (python)如何在列表中获取生成器的下一个值(python)
【发布时间】:2011-05-08 07:58:45
【问题描述】:

我制作了一个生成器,可以逐字读取文件,效果很好。

def word_reader(file):
    for line in open(file):
        for p in line.split():
            yield p

reader = word_reader('txtfile')
next(reader)

获取列表中的 n 个下一个值的最简单方法是什么?

【问题讨论】:

标签: python list generator


【解决方案1】:

使用itertools.islice:

list(itertools.islice(it, n))

【讨论】:

  • 步长值默认为1,因此可以省略:list(itertools.islice(it, 0, n))
  • @Dave 是的,确实如此。 0 也可以省略,因为它是可选的。
  • 考虑islice() 的论点的一种简单方法是它们完全反映了range() 的论点:islice([start,] stop[, step])(限制该步骤> 0)
  • @BeniCherniavsky-Paskin:虽然有一个怪癖,但stop 可以显式为None,这意味着islice 对象本身永远不会停止迭代,除非底层的可迭代对象停止。在该用例中,您尝试跳过元素(start 的初始元素,step-1 的初始元素,step > 1 的产量之间的元素),不要在足够远的情况下截断输入。 range 不接受 None 作为 stop 值(itertools.count 填补了这个空白),所以使用 range 的抽象只是一个漏洞。
【解决方案2】:

编辑:使用itertools.islice。我最初提出的以下模式是个坏主意——当it 产生的值小于n 值时它会崩溃,并且这种行为取决于一些微妙的问题,因此阅读此类代码的人不太可能理解它的精确语义。

还有

[next(it) for _ in range(n)]

对于不熟悉 itertools 的人来说,哪个可能(?)更清楚;但如果您经常处理迭代器,那么 itertools 是您工具集中的一个有价值的补充。

如果next(it)用尽并引发StopIteration会发生什么?

(即当it 的值小于n 时)

几年前我写上面这行代码时,我可能认为StopIteration 会产生巧妙的副作用,即干净地终止列表理解。但是不,整个理解将向上传递StopIteration。 (只有当异常来自 range(n) 迭代器时,它才会干净地退出。)

这可能不是您想要的行为。

但情况会变得更糟。以下内容应该等同于列表推导(尤其是在 Python 3 上):

list(next(it) for _ in range(n))

不是。内部是生成器函数的简写; list() 知道它在引发 StopIteration 任何地方时就完成了。
=> 当没有n 值时,这个版本可以安全地处理并返回一个较短的列表。 (比如itertools.islice()。)

[执行于:2.73.4]

但这也将改变!当生成器内部的任何代码引发StopIteration 时,生成器会静默退出这一事实是一个已知的缺陷,由PEP 479 解决。从 Python 3.7(或未来导入的 3.5)开始,这将导致 RuntimeError 而不是干净地完成生成器。 IE。它会变得类似于列表理解的行为。 (在最近的 HEAD 版本上测试)

【讨论】:

  • 是的,也不错。我认为islice 的解决方案更好一些,所以我会接受那个。
  • 当然这个答案要好得多,因为它更简单,不需要额外的模块来导入,括号更少......也许在 Python 4 中切片默认返回生成器(与 Py3 中的映射相比) .我只会将i 更改为_,以免在某些IDE 中出现“未使用的变量”警告;)。顺便说一句,在 Haskell 中它被称为 take N,这是一个完美的功能。
  • 除非 n 大于生成器的长度,否则您将得到一个 StopIteration 和一个未定义的变量。
  • @xApple 哎呀,你是对的!如果写成 list(genartor expr.) 会令人困惑。编辑解释这一点,赞成islice
  • 如果您不介意虚假值,可以使用next函数的默认arg并调用,例如[next(it, None) for _ in range(n)]
【解决方案3】:
for word, i in zip(word_reader(file), xrange(n)):
    ...

【讨论】:

  • 这很糟糕,因为它消耗了生成器中的额外元素。贝尼的回答没有这样做。
  • 如果您这样做for i, word in zip(xrange(n), word_reader(file)):,则可以避免这种一次性行为。虽然我更喜欢可靠的错误而不是这种脆弱的依赖于订单的“修复”:-)
  • 这似乎是仅使用原语最简单的方法。
【解决方案4】:

要获取生成器的前 n 个值,可以使用 more_itertools.take

如果您打算以块的形式迭代单词(例如,一次 100 个),您可以使用 more_itertools.chunked (https://more-itertools.readthedocs.io/en/latest/api.html):

import more_itertools
for words in more_itertools.chunked(reader, n=100):
    # process 100 words

【讨论】:

  • 我在 more_itertools 中查看了take 的源代码,在我看来take 的定义只是list(islice(iterable, n))。这个,如果你不想为此安装单独的包,使用islice解决方案应该没有缺点。
【解决方案5】:

使用cytoolz.take

>>> from cytoolz import take
>>> list(take(2, [10, 20, 30, 40, 50]))
[10, 20]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-26
    • 1970-01-01
    • 2015-01-16
    • 1970-01-01
    • 2015-12-05
    • 1970-01-01
    • 2022-08-15
    • 2022-08-08
    相关资源
    最近更新 更多