2013-09-26 7 views
7

Ho scritto una classe che legge un file txt. Il file è composto da blocchi di linee non vuote (chiamiamoli "sezioni"), separati da una riga vuota:generatore python di generatori?

line1.1 
line1.2 
line1.3 

line2.1 
line2.2 

La mia prima implementazione è stato quello di leggere l'intero file e tornare una lista di liste, che è un elenco di sezioni, in cui ogni sezione è un elenco di righe. Questo era ovviamente terribile in termini di memoria.

Così l'ho ri-implementato come un generatore di liste, cioè ad ogni ciclo la mia classe legge un'intera sezione in memoria come una lista e la produce.

Questo è meglio, ma è ancora problematico in caso di sezioni di grandi dimensioni. Quindi mi chiedo se posso reimplementarlo come un generatore di generatori? Il problema è che questa classe è molto generica e dovrebbe essere in grado di soddisfare entrambi questi casi d'uso:

  1. leggere un file molto grande, contenente sezioni molto grandi, e scorrere solo una volta. Un generatore di generatori è perfetto per questo.
  2. legge un file piccolo in memoria per essere ripetuto più volte. Un generatore di liste funziona bene, perché l'utente può semplicemente richiamare

    lista (MyClass (file_handle))

Tuttavia, un generatore di generatori non avrebbe funzionato nel caso 2, come gli oggetti interni no essere trasformato in liste.

Esiste qualcosa di più elegante dell'implementazione di un metodo to_list() esplicito, che trasformerebbe il generatore di generatori in un elenco di elenchi?

+0

Hai provato a lavorare con readline. In questo modo viene letta solo una linea singola; delimitato da una nuova riga. Questo è un buon modo per caricare piccoli dati nella memoria, a meno che le linee stesse siano enormi. – Vivek

+0

@Vivek Le mie linee sono molto complesse e da ognuna di esse genero un oggetto che convalida la linea e il cui stato dipende anche dalle linee precedenti. L'esposizione della formattazione interna del file all'utente non è un'opzione. – crusaderky

+0

puoi dare una riga di input di esempio ... – Vivek

risposta

6

Python 2:

map(list, generator_of_generators) 

Python 3:

list(map(list, generator_of_generators)) 

o per entrambi:

[list(gen) for gen in generator_of_generators] 

Poiché gli oggetti generati sono generator functions, non semplici generatori, è 'voglio fare

[list(gen()) for gen in generator_of_generator_functions] 

Se ciò non funziona, non ho idea di cosa stai chiedendo. Inoltre, perché restituirebbe una funzione di generatore e non un generatore stesso?


Dal momento che nei commenti hai detto che volevi evitare list(generator_of_generator_functions) da crash misteriosamente, questo dipende da ciò che si vuole veramente.

  • È Non possibile sovrascrivere il comportamento di list in questo modo: o si memorizzare gli elementi sub-generatore o non

  • Se davvero vuole ricevere un incidente, vi consiglio esaurire il sub-generatore con il loop principale del generatore ogni volta che il generatore principale itera. Questa è una pratica standard ed esattamente ciò che fa itertools.groupby, un generatore di generatori stdlib.

es.

def metagen(): 
    def innergen(): 
     yield 1 
     yield 2 
     yield 3 

    for i in range(3): 
     r = innergen() 
     yield r 

     for _ in r: pass 
  • Oppure utilizzare un metodo trucco oscuro segreto che vi mostrerò in un mo'(ho bisogno di scrivere), ma non lo faccio!

Come promesso, l'hack (per Python 3, questa volta 'round):

from collections import UserList 
from functools import partial 


def objectitemcaller(key): 
    def inner(*args, **kwargs): 
     try: 
      return getattr(object, key)(*args, **kwargs) 
     except AttributeError: 
      return NotImplemented 
    return inner 


class Listable(UserList): 
    def __init__(self, iterator): 
     self.iterator = iterator 
     self.iterated = False 

    def __iter__(self): 
     return self 

    def __next__(self): 
     self.iterated = True 
     return next(self.iterator) 

    def _to_list_hack(self): 
     self.data = list(self) 
     del self.iterated 
     del self.iterator 
     self.__class__ = UserList 

for key in UserList.__dict__.keys() - Listable.__dict__.keys(): 
    if key not in ["__class__", "__dict__", "__module__", "__subclasshook__"]: 
     setattr(Listable, key, objectitemcaller(key)) 


def metagen(): 
    def innergen(): 
     yield 1 
     yield 2 
     yield 3 

    for i in range(3): 
     r = Listable(innergen()) 
     yield r 

     if not r.iterated: 
      r._to_list_hack() 

     else: 
      for item in r: pass 

for item in metagen(): 
    print(item) 
    print(list(item)) 
#>>> <Listable object at 0x7f46e4a4b850> 
#>>> [1, 2, 3] 
#>>> <Listable object at 0x7f46e4a4b950> 
#>>> [1, 2, 3] 
#>>> <Listable object at 0x7f46e4a4b990> 
#>>> [1, 2, 3] 

list(metagen()) 
#>>> [[1, 2, 3], [1, 2, 3], [1, 2, 3]] 

E' così male che non voglio spiegare anche esso.

La chiave è che si dispone di un wrapper in grado di rilevare se è stato iterato, e se non si esegue un _to_list_hack che, non scherzo, cambia l'attributo __class__.

A causa di layout in conflitto, dobbiamo usare la classe UserList e shadow tutti i suoi metodi, che è solo un altro livello di crud.

Fondamentalmente, si prega di non utilizzare questo hack. Puoi godertelo come umorismo, però.

0

Un modo piuttosto pragmatico sarebbe dire al "generatore di generatori" al momento della creazione se generare generatori o elenchi. Anche se non è così conveniente come avere list magicamente sapere cosa fare, sembra comunque essere più comodo rispetto ad avere una speciale funzione to_list.

def gengen(n, listmode=False): 
    for i in range(n): 
     def gen(): 
      for k in range(i+1): 
       yield k 
     yield list(gen()) if listmode else gen() 

A seconda del parametro listmode, Questo può essere utilizzato per generare i generatori o elenchi.

for gg in gengen(5, False): 
    print gg, list(gg) 
print list(gengen(5, True))