2010-02-27 3 views
9

Я ищу способ «пропустить» через итератор Python. То есть, я хотел бы обернуть заданный итератор итератором и page_size с другим итератором, который бы возвращал элементы из iter в виде серии «страниц». Каждая страница сама была бы итератором до page_size итераций.Как написать пейджер для итераторов Python?

Я просмотрел 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 раза кажется kludgy для меня. Один клон должен проверить, есть ли у self.iter больше предметов. Я решил пойти с a technique Alex Martelli suggested (понимая, что он написал wrapping technique). Второй клон состоял в том, чтобы позволить возвращенной странице быть независимой от внутреннего итератора (self.iter). Есть ли способ избежать создания 3 клонов?
  3. Есть ли лучший способ справиться с исключением исключений, кроме того, чтобы поймать его, а затем снова поднять его? Я искушаюсь не поймать его вообще, и пусть это пузырится.

Спасибо! -Raymond

+1

Похожие: http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python http://stackoverflow.com/questions/434287/what-is-the-most-pythonic-way-to-iterate-over-a-list-in-chunks http://stackoverflow.com/questions/1335392/iteration-over-list-slices http : //stackoverflow.com/questions/760753/iterate-over-a-python-sequence-in-multiples-of-n – jfs

ответ

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

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

+0

Спасибо, что ответили на мой вопрос и предоставили хороший способ подумать о том, как просто пройти через итератор. Я думаю, что есть небольшая ошибка - вы хотели добавить этот элемент на страницу - как в: def grouper (page_size, iterable): страница = [] для элемента в истребителе: , если len (страница) == PAGE_SIZE: страница выход страница = [] еще: page.append (пункт) выход страницы –

+0

@raymondyee: На самом деле, есть лучший путь. Ваша версия имеет большой размер. Попытайтесь увидеть, что он пропускает элемент. –

+0

@ S.Lott - да, конечно, я поставил свой page.append (item) в неправильном месте. Спасибо за исправление. Я все еще узнаю, когда может помочь itertools, и когда в этом нет необходимости. Любые рекомендации, предлагаемые? –

7

Посмотрите на grouper() на itertools recipes.

+0

Спасибо, что указали рецепты. Я вижу, как с помощью группы, потому что он эффективен и адаптирует рецепт, чтобы вести себя точно так же, как мой пейджер. Мне все еще любопытно, стоит ли у Пейджера стоять много достоинства - или я должен отказаться от него для подхода, подобного группе. –

0

Основываясь на указателе на рецепт 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)) 

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

2

Я хотел бы сделать это следующим образом:

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

Спасибо, Мэтт. Вы заставили меня понять, что я оба не позволял Никому быть законным значением от итератора, и я не учитывал значение fillvalue. –

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