[Python-ideas] Propagating StopIteration value
Oscar Benjamin
oscar.j.benjamin at gmail.com
Tue Oct 9 15:07:45 CEST 2012
On 7 October 2012 23:43, Oscar Benjamin <oscar.j.benjamin at gmail.com> wrote:
>
> Before pep 380 filter(lambda x: True, obj) returned an object that was
> the same kind of iterator as obj (it would yield the same values). Now
> the "kind of iterator" that obj is depends not only on the values that
> it yields but also on the value that it returns. Since filter does not
> pass on the same return value, filter(lambda x: True, obj) is no
> longer the same kind of iterator as obj. The same considerations apply
> to many other functions such as map, itertools.groupby,
> itertools.dropwhile.
>
I really should have checked this before posting but I didn't have
Python 3.3 available:
Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:55:48) [MSC v.1600
32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import itertools
>>>
>>> def f():
... return 'Returned from generator!'
... yield
...
>>> next(filter(lambda x:True, f()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: Returned from generator!
So filter does propagate the same StopIteration instance. However map does not:
>>> next(map(None, f()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
The itertools module is inconsistent in this respect as well. As
already mentioned itertools.chain() hides the value:
>>> next(itertools.chain(f(), f()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> next(itertools.chain(f()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Other functions may or may not:
>>> next(itertools.dropwhile(lambda x:True, f()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: Returned from generator!
>>> next(itertools.groupby(f()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
These next two seem wrong since there are two iterables (but I don't
think they can be done differently):
>>> def g():
... return 'From the other generator...'
... yield
...
>>> next(itertools.compress(f(), g()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: Returned from generator!
>>> next(zip(f(), g()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: Returned from generator!
I guess this should be treated as undefined behaviour? Perhaps it
should be documented as such so that anyone who chooses to rely on it
was warned.
Also some of the itertools documentation is ambiguous in relation to
returning vs yielding values from an iterator. Those on the builtin
functions page are defined carefully:
http://docs.python.org/py3k/library/functions.html#filter
filter(function, iterable)
Construct an iterator from those elements of iterable for which
function returns true.
http://docs.python.org/py3k/library/functions.html#map
map(function, iterable, ...)
Return an iterator that applies function to every item of
iterable, yielding the results.
But some places in the itertools module use 'return' in place of 'yield':
http://docs.python.org/py3k/library/itertools.html#itertools.filterfalse
itertools.filterfalse(predicate, iterable)
Make an iterator that filters elements from iterable returning
only those for which the predicate is False. If predicate is None,
return the items that are false.
http://docs.python.org/py3k/library/itertools.html#itertools.groupby
itertools.groupby(iterable, key=None)
Make an iterator that returns consecutive keys and groups from the
iterable. The key is a function computing a key value for each
element. If not specified or is None, key defaults to an identity
function and returns the element unchanged.
Oscar
More information about the Python-ideas
mailing list