2012-07-14 11 views
41

PEP 342 (Coroutines via Enhanced Generators) aggiunto un metodo throw() agli oggetti generatore, che consente al chiamante di sollevare un'eccezione all'interno del generatore (come se fosse stata generata dall'espressione yield).Per cosa è generator.throw()?

Mi chiedo quali sono i casi d'uso per questa funzionalità.

+1

Contesto: attualmente sto lavorando su un'implementazione di generatore/coroutine in PHP e mi chiedo se dovrei includere o meno la funzionalità 'throw()'. – NikiC

+3

Vuoi generatori o coroutine? Mentre Python confonde i due, e tu puoi costruire il primo da quest'ultimo, sono diversi (come in un campionato completamente diverso). – delnan

risposta

47

Diciamo che uso un generatore per gestire l'aggiunta di informazioni a un database; Io lo uso per archiviare le informazioni ricevute dalla rete e, utilizzando un generatore, posso farlo in modo efficiente ogni volta che ricevo effettivamente dati e altre cose.

Quindi, il mio generatore prima apre una connessione al database, e ogni volta che lo si invia qualcosa, sarà aggiungere una riga:

def add_to_database(connection_string): 
    db = mydatabaselibrary.connect(connection_string) 
    cursor = db.cursor() 
    while True: 
     row = yield 
     cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row) 

Questo è tutto bene e bene; ogni volta che i miei dati sono .send() inserirò una riga.

Ma cosa succede se il mio database è transazionale? Come faccio a segnalare questo generatore quando si impegnano i dati nel database? E quando interrompere la transazione? Inoltre, è in possesso di una connessione aperta al database, forse a volte voglio che chiuda quella connessione per recuperare le risorse.

Qui è dove entra il metodo .throw(); con .throw() posso alzare eccezioni a tale metodo per segnalare determinate circostanze:

def add_to_database(connection_string): 
    db = mydatabaselibrary.connect(connection_string) 
    cursor = db.cursor() 
    try: 
     while True: 
      try: 
       row = yield 
       cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row) 
      except CommitException: 
       cursor.execute('COMMIT') 
      except AbortException: 
       cursor.execute('ABORT') 
    finally: 
     cursor.execute('ABORT') 
     db.close() 

Procedimento .close() su un generatore fa essenzialmente la stessa cosa; utilizza l'eccezione GeneratorExit combinata con .throw() per chiudere un generatore in esecuzione.

Tutto questo è un importante supporto di come coroutines lavoro; le coroutine sono essenzialmente generatori, insieme ad alcune sintassi aggiuntive per rendere più semplice e chiara la scrittura di una coroutine. Ma sotto il cofano sono ancora costruiti sulla stessa resa e invio. E quando si eseguono più coroutine in parallelo, è necessario un modo per uscire in modo pulito da quelle coroutine se uno di essi ha fallito, solo per citarne un esempio.

+6

Grazie per la risposta. Questo è sicuramente un caso d'uso interessante. Ma mi chiedo se questo possa essere classificato come abuso di eccezione. Impegnarsi e abortire non sono condizioni eccezionali, ma piuttosto parte del comportamento abituale. Quindi qui le eccezioni sono fondamentalmente usate come mezzo per cambiare il flusso di controllo. – NikiC

+2

@NikiC Il punto è valido per la programmazione sincrona, ma è necessario visualizzarlo nel mondo della programmazione asynch. Immagina che il blocco di prova di cui sopra sia molto più grande (chiama il codice all'interno di try, il caso di utilizzo generale) e magari anche aggiungere alcune dichiarazioni di rendimento in modo che il generatore entri ed esca durante il suo caso d'uso generale. Il metodo .throw() ci consente di "uscire" per gestire eccezioni speciali. Se hai familiarità con i gestori di interrupt, puoi pensare in questo modo. In questo modo, indipendentemente dal luogo di utilizzo, possiamo interrompere il flusso per eseguire operazioni speciali (se non critiche) –

+3

@NikiC Non c'è niente di sbagliato nell'usare le eccezioni per il controllo del flusso. – Marcin

9

A mio parere il metodo throw() è utile per molte ragioni.

  1. simmetria: non c'è una forte ragione per la quale una condizione eccezionale deve essere maneggiato solo il chiamante e non anche nella funzione del generatore. (Supponiamo che un generatore che legge i valori da un database restituisca un valore errato e supponiamo che solo il chiamante sappia che il valore è negativo. Con il metodo throw() il chiamante può segnalare al generatore che c'è una situazione anomala che deve essere corretta .) Se il generatore può generare un'eccezione, intercettata dal chiamante, dovrebbe essere possibile anche l'inverso.

  2. Flessibilità: una funzione generatore può avere più di una dichiarazione yield e il chiamante potrebbe non essere a conoscenza dello stato interno del generatore. Lanciando eccezioni è possibile reimpostare il generatore in uno stato noto o implementare un controllo di flusso più sofisticato che sarebbe molto più ingombrante con next(), send(), close() da solo.

Chiedendo casi d'uso può essere fuorviante: per ogni caso d'uso si potrebbe produrre un esempio banco senza la necessità di un metodo throw(), e la discussione potrebbe continuare per sempre ...

3

Un caso d'uso è per includere informazioni sullo stato interno di un generatore nello stack trace quando si verifica un'eccezione - informazioni che altrimenti non sarebbero visibili al chiamante.

Ad esempio, dire che abbiamo un generatore come il seguente dove lo stato interno che vogliamo è il numero di indice corrente del generatore:

def gen_items(): 
    for i, item in enumerate(["", "foo", "", "foo", "bad"]): 
     if not item: 
      continue 
     try: 
      yield item 
     except Exception: 
      raise Exception("error during index: %d" % i) 

Il seguente codice non è sufficiente ad innescare la gestione delle eccezioni supplementare:

# Stack trace includes only: "ValueError: bad value" 
for item in gen_items(): 
    if item == "bad": 
     raise ValueError("bad value") 

Tuttavia, il seguente codice fornisce la stato interno:

# Stack trace also includes: "Exception: error during index: 4" 
gen = item_generator() 
for item in gen: 
    if item == "bad": 
     gen.throw(ValueError, "bad value")