在Python中用块(n)迭代迭代器?

Ger*_*nuk 102 python iterator

你能想出一个很好的方法(可能用itertools)将迭代器拆分成给定大小的块吗?

因此l=[1,2,3,4,5,6,7]chunks(l,3)变成一个迭代[1,2,3], [4,5,6], [7]

我可以想到一个小程序来做这个,但不是一个很好的方式可能itertools.

Sve*_*ach 126

grouper() 从配方itertools文件的食谱来靠近你想要什么:

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)
Run Code Online (Sandbox Code Playgroud)

不过,它会用填充值填充最后一个块.

一种不太通用的解决方案,仅适用于序列,但可以根据需要处理最后一个块

[my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)]
Run Code Online (Sandbox Code Playgroud)

最后,对一般迭代器起作用的解决方案的行为符合要求

def grouper(n, iterable):
    it = iter(iterable)
    while True:
       chunk = tuple(itertools.islice(it, n))
       if not chunk:
           return
       yield chunk
Run Code Online (Sandbox Code Playgroud)

  • 我很惊讶这是一个如此高度投票的答案.该配方适用于小`n`,但对于大型组,效率非常低.我的n,例如,是200,000.创建一个包含200K项目的临时列表......并不理想. (6认同)
  • @barraponto:不,这是不可接受的,因为你会留下无限循环. (3认同)
  • @SvenMarnach 我们不得不不同意。我相信人们想要的是便利,而不是无偿的开销。他们得到了开销,因为文档提供了一个不必要的臃肿答案。对于大数据,临时元组/列表/等。200K 或 1M 项目使程序消耗千兆字节的多余内存并花费更长的时间来运行。如果不需要,为什么要这样做?在 200K 时,额外的临时存储使整个程序的运行时间比删除它时长 3.5 倍。就这么一改。所以这是一个很大的问题。NumPy 不起作用,因为迭代器是一个数据库游标,而不是一个数字列表。 (3认同)
  • @JonathanEunice:在几乎所有情况下,这都是人们想要的(这就是为什么它包含在Python文档中的原因)。针对特定的特殊情况进行优化超出了此问题的范围,即使使用您在评论中包含的信息,我也无法确定哪种最佳方法适合您。如果要分块适合内存的数字列表,最好使用NumPy的`.resize()`消息。如果要对通用迭代器进行分块,则第二种方法已经相当不错-它创建了大小为200K的临时元组,但这没什么大不了的。 (2认同)
  • izip_longest 在 Python 3 中重命名为 zip_longest (2认同)

rec*_*dev 52

虽然OP要求函数将块返回为列表或元组,但是如果需要返回迭代器,则可以修改Sven Marnach的解决方案:

def grouper_it(n, iterable):
    it = iter(iterable)
    while True:
        chunk_it = itertools.islice(it, n)
        try:
            first_el = next(chunk_it)
        except StopIteration:
            return
        yield itertools.chain((first_el,), chunk_it)
Run Code Online (Sandbox Code Playgroud)

一些基准测试:http://pastebin.com/YkKFvm8b

只有当你的函数遍历每个块中的元素时,它才会稍微高效一些.

  • 在找到文档中的答案(这是上面接受的,最高度投票的答案)之后,我今天几乎完全达到了这个设计*大规模*低效.当您一次对数十万或数百万个对象进行分组时 - 这是您最需要分割的时候 - 它必须非常高效.这是正确的答案. (16认同)
  • 如果调用者没有耗尽 `chunk_it` (例如,提前中断内部循环),这会不会表现错误? (5认同)
  • 聚会有点晚了:这个优秀的答案可以通过用 for 循环替换 while 循环来缩短一点:“for x in it:yield chain((x,), islice(it, n))”,对吧? (4认同)

Mik*_*ike 30

Python 3.12 添加了itertools.batched,它适用于所有可迭代对象(包括列表):

>>> from itertools import batched
>>> for batch in batched('ABCDEFG', 3):
...     print(batch)
('A', 'B', 'C')
('D', 'E', 'F')
('G',)
Run Code Online (Sandbox Code Playgroud)


Céd*_*YER 22

从 python 3.8 开始,有一个使用:=运算符的更简单的解决方案:

def grouper(iterator: Iterator, n: int) -> Iterator[list]:
    while chunk := list(itertools.islice(iterator, n)):
        yield chunk
Run Code Online (Sandbox Code Playgroud)

然后这样称呼它:

>>> list(grouper(iter('ABCDEFG'), 3))
[['A', 'B', 'C'], ['D', 'E', 'F'], ['G']]
Run Code Online (Sandbox Code Playgroud)

注意:您可以放入iter函数grouper以取 anIterable而不是Iterator

  • 请注意,传递一个列表(如OP示例中的“l”)会直接导致“grouper”中的无限循环。改为传递 `iter(l)`,或相应地修改函数。 (5认同)

小智 12

这适用于任何可迭代的.它返回发电机的发电机(充分灵活).我现在意识到它与@reclosedevs解决方案基本相同,但没有绒毛.无需try...exceptStopIteration传播了起来,这是我们想要的.

next(iterable)需要调用,以提高StopIteration当迭代是空的,因为islice将永远继续下去产卵空的发电机,如果你让它.

它更好,因为它只有两行,但很容易理解.

def grouper(iterable, n):
    while True:
        yield itertools.chain((next(iterable),), itertools.islice(iterable, n-1))
Run Code Online (Sandbox Code Playgroud)

请注意,它next(iterable)被放入元组中.否则,如果next(iterable)它本身是可迭代的,那么itertools.chain就会把它弄平.感谢Jeremy Brown指出这个问题.

  • 为while循环添加一行"iterable = iter(iterable)"以将*iterable*转换为*iterator*可能是有意义的.[***Iterables*没有`__next__`方法.**](/sf/ask/691889271/) (3认同)
  • 虽然这可能会回答这个问题,包括解释和描述的某些部分可能有助于理解您的方法,并启发我们为什么您的答案脱颖而出 (2认同)
  • iterable.next() 需要由迭代器包含或产生,链才能正常工作 - 例如。yield itertools.chain([iterable.next()], itertools.islice(iterable, n-1)) (2认同)
  • `next(iterable)`,而不是 `iterable.next()`。 (2认同)
  • 自 PEP479 起,不推荐在生成器函数中引发 StopIteration。所以我更喜欢@reclesedevs 解决方案的显式返回语句。 (2认同)
  • @loutre 确实在 python 3.7 中它引发了一个异常...... (2认同)

eid*_*orb 7

我今天正在研究一些事情,想出了一个简单的解决方案.它类似于jsbueno的答案,但我相信group当长度iterable被整除时,他会产生空的n.我的答案在iterable疲惫时做了简单的检查.

def chunk(iterable, chunk_size):
    """Generate sequences of `chunk_size` elements from `iterable`."""
    iterable = iter(iterable)
    while True:
        chunk = []
        try:
            for _ in range(chunk_size):
                chunk.append(iterable.next())
            yield chunk
        except StopIteration:
            if chunk:
                yield chunk
            break
Run Code Online (Sandbox Code Playgroud)