Я ищу способ "пролистать" итератор Python. Таким образом, я хотел бы перенести данный проход итератора и page_size с другим итератором, который будет возвращать объекты из прохода как ряд "страниц". Каждая страница самостоятельно была бы итератором с до 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)
Я ищу некоторую обратную связь и имею следующие вопросы:
Спасибо! - Raymond
Почему вы не используете это?
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
.
Чего еще вы хотите?
Основываясь на указателе на рецепт itertools для grouper (), я придумал следующую адаптацию 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))
Я был бы рад получить отзывы о том, что я могу сделать, чтобы улучшить это код.
Я бы сделал так:
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
отфильтрован, и он не может быть элементом итератора.