2012-07-06 2 views
35

Ho un generatore e una funzione che consuma:gestire un'eccezione gettato in un generatore di

def read(): 
    while something(): 
     yield something_else() 

def process(): 
    for item in read(): 
     do stuff 

Se il generatore genera un'eccezione, voglio elaborare che nella funzione di consumatori e quindi continuare a consumare l'iteratore finché non è esaurito. Si noti che non voglio avere alcun codice di gestione delle eccezioni nel generatore.

ho pensato a qualcosa di simile:

reader = read() 
while True: 
    try: 
     item = next(reader) 
    except StopIteration: 
     break 
    except Exception as e: 
     log error 
     continue 
    do_stuff(item) 

ma questo sembra piuttosto imbarazzante per me.

risposta

38

Quando un generatore genera un'eccezione, esce. Non puoi continuare a consumare gli articoli che genera.

Esempio:

>>> def f(): 
...  yield 1 
...  raise Exception 
...  yield 2 
... 
>>> g = f() 
>>> next(g) 
1 
>>> next(g) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in f 
Exception 
>>> next(g) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
StopIteration 

Se controlli il codice del generatore, è possibile gestire l'eccezione all'interno del generatore; in caso contrario, dovresti cercare di evitare un'eccezione.

+1

Grazie! Questo sembra essere il caso. Puoi dare un'occhiata alla domanda successiva: http://stackoverflow.com/q/11366892/989121? – georg

5

Questo è anche qualcosa che non sono sicuro se gestisco correttamente/elegantemente.

Quello che faccio è a yield un Exception dal generatore e quindi lo sollevo da qualche altra parte. Come:

class myException(Exception): 
    def __init__(self, ...) 
    ... 

def g(): 
    ... 
    if everything_is_ok: 
     yield result 
    else: 
     yield myException(...) 

my_gen = g() 
while True: 
    try: 
     n = next(my_gen) 
     if isinstance(n, myException): 
      raise n 
    except StopIteration: 
     break 
    except myException as e: 
     # Deal with exception, log, print, continue, break etc 
    else: 
     # Consume n 

In questo modo mi porto ancora sul Eccezione senza alzare esso, che avrebbe causato la funzione del generatore di fermarsi. Lo svantaggio principale è che ho bisogno di controllare il risultato ottenuto con isinstance ad ogni iterazione. Non mi piace un generatore che può produrre risultati di diversi tipi, ma usarlo come ultima risorsa.

+1

Grazie, questo è simile a quello che ho fatto (vedi [questa risposta] (http://stackoverflow.com/questions/11366892/handle-generator-exceptions-in-its-consumer)) – georg

+0

Grazie a @georg per indicare fuori quella risposta. Produrre una 'tupla' con' Exception' è, penso, una soluzione migliore. – dojuba

3

Ho avuto bisogno di risolvere questo problema un paio di volte e mi sono imbattuto in questa domanda dopo una ricerca per quello che altre persone hanno fatto.

Un'opzione che richiede un po 'di refactoring potrebbe essere l'eccezione nel generatore (a un altro generatore di gestione degli errori) anziché nel generatore raise. Ecco come potrebbe essere:

def read(handler): 
    # the handler argument fixes errors/problems separately 
    while something(): 
     try: 
      yield something_else() 
     except Exception as e: 
      handler.throw(e) 
    handler.close() 

def err_handler(): 
    # a generator for processing errors 
    while True: 
     try: 
      yield 
     except Exception1: 
      handle_exc1() 
     except Exception2: 
      handle_exc2() 
     except Exception3: 
      handle_exc3() 
     except Exception: 
      raise 

def process(): 
    handler = err_handler() 
    for item in read(handler): 
     do stuff 

Questa non è sempre la soluzione migliore, ma è sicuramente un'opzione.

EDIT:

Si potrebbe rendere il tutto un po 'più bello con un decoratore in questo modo (non ho ancora testato questo, ma dovrebbe funzionare, EDIT: non funziona, mi sistemerò in un secondo momento, ma il idea è valida):

def handled(handler): 
    """ 
    A decorator that applies error handling to a generator. 

    The handler argument received errors to be handled. 

    Example usage: 

    @handled(err_handler()) 
    def gen_function(): 
     yield the_things() 
    """ 
    def handled_inner(gen_f): 
     def wrapper(*args, **kwargs): 
      g = gen_f(*args, **kwargs) 
      while True: 
       try: 
        yield from g 
       except Exception as e: 
        handler.throw(e) 
     return wrapper 
    return handled_inner 

@handled(err_handler()) 
def read(): 
    while something(): 
     yield something_else() 

def process(): 
    for item in read(): 
     do stuff