2010-02-27 8 views
9

Sto cercando un modo per "sfogliare" un iteratore Python. Cioè, mi piacerebbe racchiudere un dato iteratore iter e page_size con un altro iteratore che restituirebbe gli elementi da iter come una serie di "pagine". Ogni pagina sarebbe essa stessa un iteratore con un massimo di page_size iterazioni.Come scrivere un cercapersone per gli iteratori Python?

Ho guardato attraverso itertools e la cosa più vicina che ho visto è itertools.islice. In un certo senso, quello che mi piacerebbe è il contrario di itertools.chain - invece di concatenare una serie di iteratori in un unico iteratore, mi piacerebbe rompere un iteratore in una serie di iteratori più piccoli. Mi aspettavo di trovare una funzione di paging in itertools ma non ho potuto individuarne una.

Mi è venuta in mente la seguente classe di cercapersone e dimostrazione.

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) 

Sto cercando un feedback ed ho le seguenti domande:

  1. C'è un pager già itertools che serve un cercapersone che sto con vista?
  2. La clonazione di self.iter 3 volte sembra triste per me. Un clone è per verificare se self.iter ha altri elementi. Ho deciso di andare con a technique Alex Martelli suggested (consapevole che ha scritto di un wrapping technique). Il secondo clone consisteva nell'abilitare che la pagina restituita fosse indipendente dall'iteratore interno (self.iter). C'è un modo per evitare di creare 3 cloni?
  3. Esiste un modo migliore per gestire l'eccezione StopIteration a seguito dell'acquisizione e del successivo sollevamento? Sono tentato di non prenderlo affatto e farlo esplodere.

Grazie! -Raymond

+1

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

risposta

4

Perché non stai usando questo?

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

"Ogni pagina sarebbe di per sé un iteratore con elementi fino a page_size". Ogni pagina è un semplice elenco di elementi, che è iterabile.È possibile utilizzare yield iter(page) per produrre l'iteratore anziché l'oggetto, ma non vedo come ciò migliori qualsiasi cosa.

Alla fine viene aggiunto uno standard StopIteration.

Cosa vorresti di più?

+0

Grazie per aver risposto alla mia domanda e aver fornito un buon modo per pensare a come far passare semplicemente l'iteratore. Credo che ci sia un piccolo errore - Intendi per aggiungere l'articolo alla pagina - come in: def cernia (PAGE_SIZE, iterable): page = [] per la voce in iterabile: se len (pagina) == PAGE_SIZE: Produzione pagine page = [] altro: page.append (voce) pagina resa –

+0

@raymondyee: In realtà, non c'è un modo migliore. La tua versione ospita un grande. Prova a vedere che salta un oggetto. –

+0

@ S.Lott - sì, certo, ho messo la mia pagina.appendi (oggetto) nel posto sbagliato. Grazie per la correzione. Sto ancora imparando quando itertools può aiutare e quando non ce n'è bisogno. Qualche linea guida da offrire? –

7

Vedere grouper() nello itertools recipes.

+0

Grazie per aver indicato le ricette. Riesco a vedere usando la cernia perché è efficiente e adattare la ricetta per comportarsi esattamente come il mio cercapersone. Sono ancora curioso di sapere se Pager così com'è abbia molto valore - o dovrei abbandonarlo per un approccio da cernia. –

0

Sulla base del puntatore alla ricetta itertools per grouper(), ho trovato il seguente adattamento di grouper() per simulare Pager. Volevo filtrare eventuali Nessuno risultati e volevo restituire un iteratore piuttosto che una tupla (anche se ho il sospetto che ci potrebbe essere un piccolo vantaggio nel fare questa conversione)

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

sarei il benvenuto feedback su come quello che posso fai per migliorare questo codice.

2

lo farei in questo modo:

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) 

In questo modo, None può essere un valore legittimo che l'iteratore sputa fuori. Solo l'oggetto singolo fillvalue è stato escluso e non può essere un elemento dell'iterazione.

+0

Grazie, Matt. Mi hai fatto capire che non stavo permettendo a nessuno di essere un valore legittimo dall'iteratore e non stavo rendendo conto del valore di riempimento. –

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