iterators: class vs generator

C

Clark C. Evans

There is an interesting difference between how exceptions are handled
between iterators constructed from a class, and iterators constructed
from a generator. The following are "mostly equivalent":

class iterable_via_class:
def __iter__(self):
self._next = self._one
return self
def next(self):
return self._next()
def _one(self):
self._next = self._two
return "one"
def _two(self):
self._next = self._stop
return "two"
def _stop(self):
raise StopIteration()

def iterable_via_generator():
yield "one"
yield "two"

print "\nclass:"
for x in iterable_via_class():
print x

print "\ngenerator:"
for x in iterable_via_generator():
print x

However, when exceptions are involved, behavior can be different:

class iterable_via_class:
def __iter__(self):
self._next = self._one
return self
def next(self):
return self._next()
def _one(self):
self._next = self._raise
return "one"
def _raise(self):
self._next = self._two
raise Exception()
def _two(self):
self._next = self._stop
return "two"
def _stop(self):
raise StopIteration()

def iterable_via_generator():
yield "one"
raise Exception()
yield "two"

def test(itergen):
it = iter(itergen())
print it.next()
try:
ignore = it.next()
except:
pass
print it.next()

print "\nclass:"
test(iterablex)
print "\ngenerator:"
test(iterable)
 
M

Michael Hudson

Clark C. Evans said:
There is an interesting difference between how exceptions are handled
between iterators constructed from a class, and iterators constructed
from a generator. The following are "mostly equivalent":
[...]

However, when exceptions are involved, behavior can be different:

(It would have been nice if you'd explained what the differences were
in your post, and given that you'd didn't if you'd made sure the
examples were correct -- got a NameError for iterable...)

Here's a better illustration:
.... yield "one"
.... raise Exception()
.... yield "two"
.... Traceback (most recent call last):
File "<stdin>", line 1, in ?
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration

I agree this is surprising. I am vaguely aware of some effort being
put in to make generators "stick", i.e. this doesn't surprise me so
much:
.... yield 1
.... return
.... yield 2
.... Traceback (most recent call last):
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration

I'd guess the behaviour you've noticed is an unintended consequence of
this. I'd also be willing to be persuaded that it's a bug, although
others might disagree, dunno. File an issue, anyway?

Cheers,
mwh
 
A

Andrew Durdin

Here's a better illustration:

.... yield "one"
.... raise Exception()
.... yield "two"
....
Traceback (most recent call last):
File "<stdin>", line 1, in ?

Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration


This is correct behaviour according to PEP 255:
"""
If an unhandled exception-- including, but not limited to,
StopIteration --is raised by, or passes through, a generator function,
then the exception is passed on to the caller in the usual way, and
subsequent attempts to resume the generator function raise
StopIteration. In other words, an unhandled exception terminates a
generator's useful life.
"""
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,191
Latest member
BuyKetoBeez

Latest Threads

Top