5

I use function f to create generator but sometimes it can raise error. I would like two things to happen for the main code

  1. The for loop in the main block continues after catching the error
  2. In the except, print out the index that generates the error (in reality the error may not occur for index 3)

The code I came up with stops after the error is raised. How shall I implement the forementioned two features? Many thanks.

def f(n):
    for i in xrange(n):
        if i == 3:
            raise ValueError('hit 3')
        yield i

if __name__ == '__main__':
    a = enumerate(f(10))
    try:
        for i, x in a:
            print i, x
    except ValueError:
        print 'you have a problem with index x'
nos
  • 19,875
  • 27
  • 98
  • 134
  • 3
    If the exception is raised inside a generator, I see no way to resume the execution of that generator, unless of course you catch the exception inside the generator itself. – vaultah Oct 11 '16 at 17:07
  • 1
    It's not clear if you want to continue the `for` inside the iterator or the one in `main`.... – Paolo Casciello Oct 11 '16 at 17:16
  • I want the `for` in the main block to continue. Original post edited. – nos Oct 11 '16 at 17:17
  • 1
    @PaoloCasciello You can't continue the one in main without continuing the generator. – brianpck Oct 11 '16 at 17:21
  • @brianpck that's why I asked OP clarifications. :) Your solutions assumes he can modify his generator that way. But as he replied, he wants the main loop over the generator to know for which index it didn't worked. It's a different problem than what you answered. – Paolo Casciello Oct 11 '16 at 17:27
  • @PaoloCasciello If he can't modify the generator, then he can't "continue" the loop because the `ValueError` will stop the loop. Continuing the loop *requires* modifying the generator – brianpck Oct 11 '16 at 17:29
  • @brianpck of course. But still, i'm trying to say i think he can't modify it "in your way". I think his original generator is more complex than the demo he posted ;) i.e. it's not just a for with a raise – Paolo Casciello Oct 11 '16 at 17:31

3 Answers3

7

You will have to catch the exception inside your generator if you want its loop to continue running. Here is a working example:

def f(n):
    for i in xrange(n):
        try:
            if i == 3:
                raise ValueError('hit 3')
            yield i
        except ValueError:
            print ("Error with key: {}".format(i))

Iterating through it like in your example gives:

>>> for i in f(10):
...     print (i)
...
0
1
2
Error with key: 3
4
5
6
7
8
9
brianpck
  • 8,084
  • 1
  • 22
  • 33
3

As of OP clarifications, he wants to continue the outside main for loop in case of error inside the generator, showing at which index the error occurred.

An answer by brianpck takes the approach of modifying the generator so that it prints the error. This way the main loop doesn't know an error occurred at that index, and thus you have at index x-1 the results following the error. Sometimes you care of the assumption "one index <-> one result".

To solve this we can manually manage the error by yielding it and then deciding what to do in the generator.

Like to following:

def f(n):
    for i in xrange(n):
        if i == 3:
            yield ValueError('hit 3')
            continue  # or break, depends on problem logic
        yield i

if __name__ == '__main__':
    a = enumerate(f(10))
    for i, x in a:
        if isinstance(x, ValueError):
            print "Error at index", i
            continue

        print i, x

Usually it's very unlikely a generator is yielding Exception classes, so it would be safe to check if the result is an Exception and dealing with it.

Paolo Casciello
  • 7,982
  • 1
  • 43
  • 42
3

I suspect that, in general, you want to be able to catch values that result in error conditions. Without halting the loop inside the generator. Here's another approach that includes a Boolean in the result of the generator (as a 2-tuple) that indicates whether the calculation could be accomplished successfully.

def f(n):
    for i in range(n):
        accept=True
        try:
            result=1/(3-i)
        except:
            accept=False
        yield accept, i

a=enumerate(f(10))
for k,(ok,i) in a:
    print (ok,i)

In this case, only the value 3 causes failure. Here's the output.

True 0
True 1
True 2
False 3
True 4
True 5
True 6
True 7
True 8
True 9
Bill Bell
  • 21,021
  • 5
  • 43
  • 58