10

我正在寻找一种“翻页”Python 迭代器的方法。也就是说,我想用另一个迭代器包装一个给定的迭代器iterpage_size,它会将来自 iter 的项目作为一系列“页面”返回。每个页面本身都是一个迭代器,最多可进行page_size次迭代。

我查看了itertools,我看到的最接近的是itertools.islice。在某些方面,我想要的是与itertools.chain相反——而不是将一系列迭代器链接到一个迭代器中,我想将一个迭代器分解为一系列较小的迭代器。我期待在 itertools 中找到分页功能,但找不到。

我想出了以下寻呼机类和演示。

class pager(object):
    """
    takes the iterable iter and page_size to create an iterator that "pages through" iter.  That is, pager returns a series of page iterators,
    each returning up to page_size items from iter.
    """
    def __init__(self,iter, page_size):
        self.iter = iter
        self.page_size = page_size
    def __iter__(self):
        return self
    def next(self):
        # if self.iter has not been exhausted, return the next slice
        # I'm using a technique from 
        # https://stackoverflow.com/questions/1264319/need-to-add-an-element-at-the-start-of-an-iterator-in-python
        # to check for iterator completion by cloning self.iter into 3 copies:
        # 1) self.iter gets advanced to the next page
        # 2) peek is used to check on whether self.iter is done
        # 3) iter_for_return is to create an independent page of the iterator to be used by caller of pager
        self.iter, peek, iter_for_return = itertools.tee(self.iter, 3)
        try:
            next_v = next(peek)
        except StopIteration: # catch the exception and then raise it
            raise StopIteration
        else:
            # consume the page from the iterator so that the next page is up in the next iteration
            # is there a better way to do this?
            # 
            for i in itertools.islice(self.iter,self.page_size): pass
            return itertools.islice(iter_for_return,self.page_size)



iterator_size = 10
page_size = 3

my_pager = pager(xrange(iterator_size),page_size)

# skip a page, then print out rest, and then show the first page
page1 = my_pager.next()

for page in my_pager:
    for i in page:
        print i
    print "----"

print "skipped first page: " , list(page1)   

我正在寻找一些反馈并有以下问题:

  1. itertools中是否已经有一个寻呼机,它为我忽略的寻呼机提供服务?
  2. 克隆 self.iter 3 次对我来说似乎很笨拙。一个克隆是检查 self.iter 是否还有更多项目。我决定采用 Alex Martelli 建议的一种技术(知道他写了一种包装技术)。第二个克隆是使返回的页面独立于内部迭代器(self.iter)。有没有办法避免制作 3 个克隆?
  3. 除了捕获它然后再次引发它之外,还有更好的方法来处理StopIteration异常吗?我很想根本不抓住它,让它冒泡。

谢谢!-雷蒙德

4

6 回答 6

8

grouper(),来自itertools菜谱

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)
于 2010-02-27T17:43:37.833 回答
4

你为什么不用这个?

def grouper( page_size, iterable ):
    page= []
    for item in iterable:
        page.append( item )
        if len(page) == page_size:
            yield page
            page= []
    yield page

“每个页面本身都是一个迭代器,最多包含 page_size”个项目。每个页面都是一个简单的项目列表,可以迭代。您可以使用yield iter(page)来产生迭代器而不是对象,但我看不出它如何改进任何东西。

它在最后抛出了一个标准StopIteration

你还想要什么?

于 2010-02-28T11:34:13.490 回答
3

我会这样做:

def pager(iterable, page_size):
    args = [iter(iterable)] * page_size
    fillvalue = object()
    for group in izip_longest(fillvalue=fillvalue, *args):
        yield (elem for elem in group if elem is not fillvalue)

这样,None可以是迭代器吐出的合法值。只有单个对象被fillvalue过滤掉,它不可能是可迭代的元素。

于 2010-02-28T00:15:53.293 回答
0

基于指向 grouper() 的 itertools 配方的指针,我提出了 grouper() 的以下改编以模仿 Pager。我想过滤掉任何 None 结果并想返回一个迭代器而不是一个元组(尽管我怀疑进行这种转换可能没有什么优势)

# based on http://docs.python.org/library/itertools.html#recipes
def grouper2(n, iterable, fillvalue=None):
    args = [iter(iterable)] * n
    for item in izip_longest(fillvalue=fillvalue, *args):
        yield iter(filter(None,item))

我欢迎就如何改进此代码提供反馈。

于 2010-02-27T22:55:00.133 回答
0
def group_by(iterable, size):
    """Group an iterable into lists that don't exceed the size given.

    >>> group_by([1,2,3,4,5], 2)
    [[1, 2], [3, 4], [5]]

    """
    sublist = []

    for index, item in enumerate(iterable):
        if index > 0 and index % size == 0:
            yield sublist
            sublist = []

        sublist.append(item)

    if sublist:
        yield sublist
于 2014-02-20T11:32:46.687 回答
0

more_itertools.chunked将完全符合您的要求:

>>> import more_itertools
>>> list(chunked([1, 2, 3, 4, 5, 6], 3))
[[1, 2, 3], [4, 5, 6]]

如果您想要分块而不创建临时列表,您可以使用more_itertools.ichunked.

该库还有许多其他不错的选项,可用于有效地分组、窗口化、切片等。

于 2020-04-01T22:32:35.003 回答