10
votes

Comment écrire un pager pour les itérateurs de Python?

Je cherche un moyen de "page à travers" un itérateur Python. C'est-à-dire que je voudrais envelopper un itérateur donné ITER B> et Page_SIZE B> avec un autre itérateur qui permettrait de renvoyer les éléments de ITER comme une série de "pages". Chaque page serait elle-même un itérateur avec page_size b> itérations.

J'ai regardé par iTertools et la chose la plus proche que j'ai vue est ithertools.lice . À certains égards, ce que j'aimerais, c'est le contraire de iTertools.chain A> - au lieu de enchaîner une série d'itérateurs ensemble dans un itérateur, je voudrais briser un itérateur dans une série d'itérateurs plus petits. Je m'attendais à trouver une fonction de pagination dans itertools mais je ne pouvais pas localiser un. P>

J'ai proposé la classe de pager et la démonstration suivantes. P>

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 commentaires

6 Réponses :


8
votes

regarder grouper () code>, à partir du iTERTOOLS code> recettes .

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)


1 commentaires

Merci d'avoir souligné les recettes. Je peux voir utiliser le mérou car c'est efficace et adapter la recette pour se comporter exactement comme mon pager. Je suis toujours curieux de savoir si le téléavertisseur qu'il sent a beaucoup de mérite - ou je devrais l'abandonner pour une approche en forme de mérou.



0
votes

Basé sur le pointeur de la recette Itertools pour le mérou (), j'ai proposé l'adaptation suivante du mérou () à imiter le pager. Je voulais filtrer tous les résultats et vouloir retourner un itérateur plutôt qu'un tuple (bien que je soupçonne qu'il pourrait y avoir peu d'avantages pour 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))


0 commentaires

3
votes

Je le ferais comme ceci: xxx

de cette façon, Aucun peut être une valeur légitime que l'itérateur crache. seul l'objet unique FLIDVALUE filtré, et il ne peut pas éventuellement être un élément du iérêteur.


1 commentaires

Merci, Matt. Vous avez fait de me faire comprendre que je n'étais pas à la fois de ne pas laisser aucune valeur légitime de l'itérateur et que je ne comptais pas la FillValue.



4
votes

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


4 commentaires

Merci d'avoir répondu à ma question et de donner un bon moyen de réfléchir à la façon de faire de la boucle à travers l'itérateur. Je pense qu'il y a une petite erreur - avez-vous voulu ajouter l'élément à la page - comme dans: def grouper (page_size, itérêtant): page = [] pour l'élément en iTable: si len (page) == page_size : page page page = [] Sinon: page.append (item) page de rendement


@Raymondyee: En fait, il y a une meilleure façon. Votre version abrite un grand. Essayez de voir que cela saute un article.


@ S.Lott - Oui, bien sûr, je mets ma page.append (article) au mauvais endroit. Merci pour la correction. J'apprends toujours quand itTools peut aider et quand il n'y a pas besoin de le besoin. Des lignes directrices à offrir?


@RAYMONDYEE: Aucun conseil. Je n'utilise pas iterools tout cela souvent. Les fonctions de générateur sont très simples.



0
votes
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 commentaires

0
votes

plus_itétools.chunked fera exactement ce que Vous recherchez:

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


0 commentaires