Как написать пейджер для итераторов Python? - PullRequest
9 голосов
/ 27 февраля 2010

Я ищу способ "пролистать" итератор Python. То есть я хотел бы обернуть данный итератор iter и page_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 больше предметов. Я решил использовать технику, предложенную Алексом Мартелли (известно, что он написал о технике обертывания ). Второй клон должен был позволить возвращаемой странице быть независимой от внутреннего итератора ( self.iter ). Есть ли способ избежать создания 3 клонов?
  3. Есть ли лучший способ справиться с исключением StopIteration , кроме того, чтобы перехватить его и затем снова вызвать? Я испытываю искушение не ловить это вообще и позволить этому пузыриться.

Спасибо! -Raymond

Ответы [ 5 ]

7 голосов
/ 27 февраля 2010

Посмотрите на grouper() в рецептах itertools .

4 голосов
/ 28 февраля 2010

Почему вы не используете это?

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 в конце.

Что бы вы еще хотели?

2 голосов
/ 28 февраля 2010

Я бы сделал это так:

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 отфильтрован, и он не может быть элементом итерируемого.

0 голосов
/ 20 февраля 2014
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
0 голосов
/ 28 февраля 2010

На основе указателя на рецепт itertools для grouper () я придумал следующую адаптацию grouper () для имитации пейджера. Я хотел отфильтровать любые результаты 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))

Я бы хотел получить отзыв о том, как я могу улучшить этот код.

...