2012-07-06 14 views
35

Tengo un generador y una función que lo consume:controlar una excepción lanzada en un generador

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

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

Si el generador produce una excepción, quiero que procesar en la función de consumo y luego seguir consumiendo el iterador hasta que esté agotado. Tenga en cuenta que no quiero tener ningún código de manejo de excepciones en el generador.

pensé en algo así como:

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

pero esto parece bastante incómodo para mí.

Respuesta

38

Cuando un generador lanza una excepción, se cierra. No puede continuar consumiendo los artículos que genera.

Ejemplo:

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

Si controla el generador de código, se puede controlar la excepción dentro del generador; si no, debes tratar de evitar que ocurra una excepción.

+1

¡Gracias! Este parece ser el caso. ¿Podría echar un vistazo a la pregunta de seguimiento: http://stackoverflow.com/q/11366892/989121? – georg

5

Esto también es algo que no estoy seguro si manejo correctamente/con elegancia.

Lo que hago es yield un Exception desde el generador, y luego levanto en otro lugar. Al igual que:

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 

De esta manera Todavía llevo sobre la excepción sin levantarla, lo que habría provocado la función de generador para detener. El mayor inconveniente es que necesito verificar el resultado arrojado con isinstance en cada iteración. No me gusta un generador que puede producir resultados de diferentes tipos, pero lo uso como último recurso.

+1

Gracias, esto es similar a lo que terminé haciendo (ver [esta respuesta] (http://stackoverflow.com/questions/11366892/handle-generator-exceptions-in-its-consumer)) – georg

+0

Gracias @georg por señalar esa respuesta. Ceder una 'tupla' con 'Excepción' es, creo, una mejor solución. – dojuba

3

He tenido que resolver este problema un par de veces y me encontré con esta pregunta después de una búsqueda de lo que otras personas han hecho.

Una opción, que requerirá refactorizar un poco las cosas, sería throw la excepción en el generador (a otro generador de manejo de errores) en lugar de raise. Esto es lo que podría parecer:

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 

No siempre será la mejor solución, pero sin duda es una opción.

EDIT:

Se podría hacer todo sólo un poco más agradable con un decorador de esta manera (no he probado esto, pero debe trabajar, EDIT: no trabajar; lo arreglaré más tarde, pero el la idea es buena):

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 
Cuestiones relacionadas