3

I'm having issues with Python 2.7, whereby an exception raised from a generator is not catchable.

I've lost a fair amount of time, twice, with this behavior.

def gen_function():
    raise Exception("Here.")

    for i in xrange(10):
        yield i

try:
    gen_function()
except Exception as e:
    print("Ex: %s" % (e,))
else:
    print("No exception.")

Output:

No exception.
Dustin Oprea
  • 9,673
  • 13
  • 65
  • 105
  • 2
    Calling a generator does **not** execute any code. Try `next(gen_function())` instead. – Bakuriu Jan 08 '14 at 19:51
  • possible duplicate of [The Python yield keyword explained](http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained) – Bakuriu Jan 08 '14 at 19:53
  • @Bakuriu: i feel that's a bit much to go through just to answer this particular question, though a full read-through of that is recommended – Claudiu Jan 08 '14 at 19:58
  • It's also worth reading through the tutorial section on [Iterators](http://docs.python.org/2/tutorial/classes.html#iterators) and the following section on Generators, which covers most of this stuff pretty well (or at least better than you can get by trying to figure it out yourself by trial and error). – abarnert Jan 08 '14 at 20:16
  • Thanks for all of the answers. Moron that I am, I usually encounter this type of generator-related problem in another context, so I didn't recognize it. – Dustin Oprea Jan 08 '14 at 21:08

2 Answers2

4

gen_function() will give you generator object

You need to call next() function to invoke the code.

You can do it directly with next function

g = gen_function()
next(g)

or

for i in g:
    pass # or whatever you want

Both will trigger an exception

Jan Vorcak
  • 19,261
  • 14
  • 54
  • 90
  • 1
    You probably want to call the [`next` _function_](http://docs.python.org/2/library/functions.html#next), as your text explains, not the `next` _method_, as your example code does. In other words, `next(g)`. (That way, your code works with both 2.x and 3.x, but more importantly, it's more idiomatic.) – abarnert Jan 08 '14 at 20:11
2

Calling a generator just gives you the generator object. No code in the generator is actually executed, yet. Usually this isn't obvious since you often apply the generator immediately:

for x in gen_function():
    print x

In this case the exception is raised. But where? To make it more explicit when this happens I've made explicit the for ... in loop (this is essentially what it does behind-the-scenes):

generator_obj = gen_function()  # no exception
it = iter(generator_obj)  # no exception (note iter(generator_obj) is generator_obj)
while True:
    try:
        x = it.next()  # exception raised here
    except StopIteration:
        break

    print x
Claudiu
  • 224,032
  • 165
  • 485
  • 680
  • Great explanation of the under-the-covers part. It covers absolutely everything (except the irrelevant edge cases inside a comprehension in 3.0-3.3), concisely, and in a way that I think a novice can understand. I need to start keeping a list of useful links like this answer to point to for elaboration in future questions… – abarnert Jan 08 '14 at 20:14
  • @abarnert: Glad you liked it! What are the edge cases you're referring to? I'm not very familiar with Python 3 – Claudiu Jan 08 '14 at 21:17
  • 1
    See [here](http://pastebin.com/7tJ2ArUQ). You can't really explain the behavior of 3.0-3.3 by translating the loop to an equivalent explicit while/try/break loop, because the code inside the `try` is basically part of a statement. (See [here](http://stupidpythonideas.blogspot.com/2013/06/can-you-optimize-listgenexp.html) for more details than you could ever want.) But that's rarely ever relevant. – abarnert Jan 08 '14 at 21:48