2010-02-27 3 views
9

Je cherche un moyen de "parcourir" un itérateur Python. C'est-à-dire, je voudrais envelopper un itérateur donné iter et page_size avec un autre itérateur qui renverrait les éléments de iter comme une série de "pages". Chaque page serait elle-même un itérateur avec page_size itérations. J'ai regardé itertools et la chose la plus proche que j'ai vue est itertools.islice. À certains égards, ce que je voudrais, c'est le contraire de itertools.chain - au lieu de chaîner une série d'itérateurs en un seul itérateur, j'aimerais décomposer un itérateur en une série d'itérateurs plus petits. Je m'attendais à trouver une fonction de pagination dans itertools mais je ne pouvais pas en trouver une. Je suis venu avec la classe de pager et la démonstration suivantes.Comment écrire un pageur pour les itérateurs Python?

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) 

Je cherche des commentaires et nous avons les questions suivantes:

  1. est-il un téléavertisseur déjà itertools qui sert un téléavertisseur que je donne sur?
  2. Clonage self.iter 3 fois me semble kludgy. Un clone est de vérifier si self.iter a plus d'éléments. J'ai décidé d'aller avec a technique Alex Martelli suggested (conscient qu'il a écrit un wrapping technique). Le second clone devait permettre à la page renvoyée d'être indépendante de l'itérateur interne (self.iter). Y a-t-il un moyen d'éviter de faire 3 clones?
  3. Existe-t-il une meilleure façon de gérer l'exception StopIteration à côté de l'attraper, puis de le relever de nouveau? Je suis tenté de ne pas l'attraper du tout et de le laisser bouillonner.

Merci! -Raymond

+1

connexes: 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

Répondre

4

Pourquoi n'utilisez-vous pas cela?

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

"Chaque page serait elle-même un itérateur avec des éléments allant jusqu'à page_size". Chaque page est une simple liste d'éléments, ce qui est itérable.Vous pouvez utiliser yield iter(page) pour générer l'itérateur au lieu de l'objet, mais je ne vois pas comment cela améliore quoi que ce soit. Il lance un standard StopIteration à la fin.

Que demander de plus?

+0

Merci d'avoir répondu à ma question et de m'avoir donné un bon moyen de réfléchir à la façon de boucler l'itérateur. Je pense qu'il y a une petite erreur - vouliez-vous ajouter l'élément à la page - comme dans: def grouper (page_size, itérable): page = [] pour l'élément dans itérable: si len (Page) == page_size: page rendement page = [] autre: page.append (point) page de rendement –

+0

de @raymondyee: en fait, il y a une meilleure façon. Votre version héberge un gros. Essayez et voyez qu'il saute un article. –

+0

@ S.Lott - oui, bien sûr, je met ma page.append (article) au mauvais endroit. Merci pour la correction. Je suis toujours en train d'apprendre quand les outils peuvent vous aider et quand ce n'est pas nécessaire. Des lignes directrices à offrir? –

7

Regardez grouper() dans le itertools recipes.

+0

Merci d'avoir signalé les recettes. Je peux voir en utilisant le mérou parce que c'est efficace et en adaptant la recette pour se comporter exactement comme mon téléavertisseur. Je suis toujours curieux de savoir si Pager en l'état a beaucoup de mérite - ou je devrais l'abandonner pour une approche de mérou. –

0

Basé sur le pointeur vers la recette d'itertools pour grouper(), je suis venu avec l'adaptation suivante de grouper() pour imiter Pager. Je voulais filtrer les résultats Aucun et je voulais retourner un itérateur plutôt que d'un tuple (bien que je pense qu'il pourrait y avoir peu d'avantage à faire cette conversion)

# 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)) 

Je salue des commentaires sur la façon dont ce que je peux faire pour améliorer ce code.

2

Je ferais comme ça:

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) 

De cette façon, None peut être une valeur légitime que le iterator recrache. Seul l'objet unique fillvalue filtré, et il ne peut pas éventuellement être un élément de l'itérative.

+0

Merci, Matt. Vous m'avez fait réaliser que je ne permettais pas que None soit une valeur légitime de l'itérateur et je ne tenais pas compte de la valeur de remplissage. –

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 
Questions connexes