Resuming generator after handling exception outside

Viewed 43

I have a generator that handles data, but may throw an exception. This exception needs to be handled outside the generator, but I would like to resume the generator afterwards. A simple example:

def generator():
    yield "some data"
    raise ValueError("Something bad happened")
    yield "more data"


def use_generator():
    gen = generator()
    while True:
        try:
            thing = next(gen)
            do_something(thing)
        except ValueError:
            handle_error()
        except StopIteration:
            break

def do_something(thing):print(thing)
def handle_error():print("caught ValueError") 

The intended output would be something like

some data

caught ValueError

more data

This does not work, because after throwing the exception, any calls to the generator will only raise a StopIteration, so the generator can't be used after the exception. Is there a way to resume function after handling the exception outside?

One way would be to catch the Error inside the generator (I do know where and what type of error are expected) and yield it. Modified, it would look like this:

def generator2():
    yield "some data"
    try:
        raise ValueError("Something bad happnened")
    except ValueError as e:
        yield e
    yield "more data"

def use_generator2():
    gen = generator2()
    while True:
        try:
            thing = next(gen)

            if isinstance(thing, ValueError):
                raise ValueError

            do_something(thing)
        except ValueError:
            handle_error()
        except StopIteration:
            break

This version produces the expected behavior, though it's also fairly cumbersome. Is there a better way to do things?

1 Answers
def generator():
    yield "some data"
    yield "something bad happened"
    yield "more data"

def use_generator(g):
    data = next(g)
    print(data)
    if data == "something bad happened":
        raise ValueError("something bad happened")
    return g


def run(g):
    while True:
        try:
            use_generator(g)
        except ValueError:
            print("caught ValueError") 
            g = use_generator(g)
        except StopIteration:
            print("end of iteration")        
            break
        

if __name__ == '__main__':
    g = use_generator(generator())
    run(g)

explanation:

  1. generator function yields data only
  2. use_generator function uses generator and raised error which is captured by main function run. if generator raised error then its visible by use_generator function only unless you pass the error further.
  3. run function is the main function to execude the logic. main function raised StopIteration when generator is exhausted and handles ValueError too captured which is raised by use_generator function.
Related