簡體   English   中英

如何為 Python 迭代器編寫尋呼機?

[英]How to write a pager for Python iterators?

我正在尋找一種“翻閱”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異常? 我很想根本不抓住它,讓它冒泡。

謝謝! -雷蒙德

查看grouper() ,來自itertools recipes

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)

你為什么不用這個?

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

你還想要什么?

我會這樣做:

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 ,它不可能是可迭代的元素。

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

基於指向 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))

我歡迎關於如何改進此代碼的反饋。

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

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

如果您希望在不創建臨時列表的情況下進行分塊,則可以使用more_itertools.ichunked

該庫還有許多其他不錯的選項,可用於有效分組、開窗、切片等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM