2010-03-29 12 views
5

Ho studiato i generatori e credo di averlo capito, ma mi piacerebbe capire dove potrei applicarlo nel mio codice.Dove usi la funzione generatori nel tuo codice Python?

che ho in mente il seguente esempio ho letto in "Python di riferimento essenziale" libro:

# tail -f 
def tail(f): 
    f.seek(0,2) 
    while True: 
    line = f.readline() 
    if not line: 
    time.sleep(0.1) 
    continue 
    yield line 

Avete qualche altro esempio efficace in cui i generatori sono il miglior strumento per il lavoro come tail -f?

Con quale frequenza si utilizza la funzione generatori e in quale tipo di funzionalità \ parte del programma viene applicata di solito?

+1

Si prega di fare questa domanda wiki della comunità. –

risposta

6

Li uso molto quando implemento scanner (tokenizer) o quando eseguo iterazioni su contenitori di dati.

modifica: ecco un demo tokenizzatore ho utilizzato per un programma C++ evidenziare la sintassi: esempio

whitespace = ' \t\r\n' 
operators = '~!%^&*()-+=[]{};:\'"/?.,<>\\|' 

def scan(s): 
    "returns a token and a state/token id" 
    words = {0:'', 1:'', 2:''} # normal, operator, whitespace 
    state = 2 # I pick ws as first state 
    for c in s: 
     if c in operators: 
      if state != 1: 
       yield (words[state], state) 
       words[state] = '' 
      state = 1 
      words[state] += c 
     elif c in whitespace: 
      if state != 2: 
       yield (words[state], state) 
       words[state] = '' 
      state = 2 
      words[state] += c 
     else: 
      if state != 0: 
       yield (words[state], state) 
       words[state] = '' 
      state = 0 
      words[state] += c 
    yield (words[state], state) 

Usage:

>>> it = scan('foo(); i++') 
>>> it.next() 
('', 2) 
>>> it.next() 
('foo', 0) 
>>> it.next() 
('();', 1) 
>>> it.next() 
(' ', 2) 
>>> it.next() 
('i', 0) 
>>> it.next() 
('++', 1) 
>>> 
+0

Potresti pubblicare qualche semplice frammento di tokenizer? – systempuntoout

+0

@systempuntoout, ok, ho postato un esempio. –

+0

Buon esempio, molte grazie! – systempuntoout

4

Ogni volta che il codice sarebbe né generare un numero illimitato di valori o più in generale, se troppa memoria sarebbe consumata generando l'intera lista in un primo momento.

O se è probabile che non si scorrere i intero elenco generato (e la lista è molto grande). Voglio dire, non ha senso generare ogni valore prima (e aspettare la generazione) se non viene usato.

Il mio ultimo incontro con i generatori è stato quando ho implementato una sequenza ricorrente lineare (LRS) come ad es. la sequenza di Fibonacci.

+1

-1: mi sembra più una descrizione degli iteratori in generale, non delle funzioni del generatore, quindi manca il punto. Perché questa risposta ha avuto qualche risultato? – nikow

+0

@nikow: Sì, è più generale, ma non direi che è una * descrizione * degli iteratori. È una descrizione astratta su in quali situazioni i generatori potrebbero essere utili. I generatori sono una sorta di iteratori .. :) –

1

In genere, per acquisizione di dati separati (che potrebbero essere complicato) dal consumo. In particolare:

  • per concatenare i risultati di diverse query B-tree - la parte db genera ed esegue le query yield -ing record da ciascuno di essi, il consumatore vede solo singole voci di dati in arrivo.
  • buffering (read-ahead) - il generatore recupera i dati in blocchi e produce singoli elementi da ciascun blocco. Di nuovo, il consumatore è separato dai dettagli cruenti.

I generatori possono funzionare anche come coroutine. È possibile passare i dati a utilizzando nextval=g.next(data) sul lato 'consumer' e data = yield(nextval) sul lato generatore. In questo caso il generatore e il suo consumatore "scambiano" i valori. È anche possibile eseguire yield un'eccezione nel contesto del generatore: lo fa g.throw(exc).

+0

Il buffering è un ottimo esempio, grazie. – systempuntoout

2

In tutti i casi in cui ho algoritmi che leggono qualcosa, utilizzo esclusivamente i generatori.

Perché?

Stratificare le regole di filtro, mappatura e riduzione è molto più semplice in un contesto di più generatori.

Esempio:

def discard_blank(source): 
    for line in source: 
     if len(line) == 0: 
      continue 
     yield line 

def clean_end(source): 
    for line in source: 
     yield line.rstrip() 

def split_fields(source): 
    for line in source; 
     yield line.split() 

def convert_pos(tuple_source, position): 
    for line in tuple_source: 
     yield line[:position]+int(line[position])+line[position+1:] 

with open('somefile','r') as source: 
    data= convert_pos(split_fields(discard_blank(clean_end(source))), 0) 
    total= 0 
    for l in data: 
     print l 
     total += l[0] 
    print total 

La mia preferenza è quella di utilizzare tanti piccoli generatori in modo che un piccolo cambiamento non è dannoso per l'intera catena di processo.

+0

Le funzioni non funzionano altrettanto bene? –

+2

Quindi state semplicemente usando le funzioni del generatore come notazione conveniente per i decoratori iteratori. Penso che l'esempio di Nick D sia molto migliore, dal momento che evidenzia l'aspetto di continuazione. – nikow

+0

@J. T. Hurley: Non so cosa significhi "altrettanto bene", ma i generatori non creano risultati intermedi, dove generalmente le funzioni lo fanno. I generatori annidati sono una sorta di pipeline per ridurre le mappe. –