On Tue, Oct 12, 2021 at 8:43 PM Oscar Benjamin
<oscar.j.benja...@gmail.com> wrote:
> A leaky StopIteration can wreak all sorts of havoc. There was a PEP that 
> attempted to solve this by turning StopIteration into RuntimeError if it gets 
> caught in a generator but that PEP (which was rushed through very quickly 
> IIRC) missed the fact that generators are not the only iterators. It remains 
> a problem that leaking a StopIteration into map, filter etc will terminate 
> iteration of an outer loop.
>

Generators are special because they never mention StopIteration. They
are written like functions, but behave like iterators. That is why
StopIteration leaking is such a problem.

In every other situation, StopIteration is part of the API of what
you're working with. It is a bug to call next() without checking for
StopIteration (or knowingly and intentionally permitting it to
bubble).

> The culprit for the problem of leaking StopIteration is next itself which in 
> the 1-arg form is only really suitable for use when implementing an iterator 
> and not for the much more common case of simply wanting to extract something 
> from an iterable. Numerous threads here and on stackoverflow and elsewhere 
> suggesting that you can simply use next(iter(obj)) are encouraging bug magnet 
> code. Worse, the bug when it arises will easily manifest in something like 
> silent data loss and can be hard to debug.
>

That's no worse than getattr() and AttributeError. If you call getattr
and you aren't checking for AttributeError, then you could be running
into the exact same sorts of problems, because AttributeError is part
of the function's API.

> The correct usage of next/iter in most cases would be something like:
>
> try:
>     val = next(iter(obj))
> except StopIteration:
>     raise AnotherError

Yes. Or whatever other method you have for coping with the lack of a
first element.

> or perhaps
>
> val = next(iter(obj), None)
> if val is None:
>     raise AnotherError

Definitely not. The two-arg form is a short-hand for this:

try:
    val = next(iter(obj))
except StopIteration:
    val = None

If your except clause would simply set a default, use two-arg next.
Otherwise, don't open yourself up to data-specific bugs.

> The real advantage of providing first (or "take" or any of the other names 
> that have been proposed in the past) is that it should raise a different 
> exception like ValueError so that it would be safe to use by default.
>

ValueError is no safer. The first() function would have, as its API,
"returns the first element or raises ValueError if there is none". So
now the caller of first() has to use try/except to handle the case
where there is no value. Failing to do so is *just as buggy* as
leaking a StopIteration.

A leaky StopIteration is a majorly confusing bug inside a __next__
function, because StopIteration is part of that function's API. A
leaky KeyError is a majorly confusing bug inside a __getitem__
function, for the same reason. A leaky AttributeError inside a
__getattr__ function, ditto. Anywhere else, those exceptions will all
just bubble up normally, and most likely get printed to the console.

>>> def leak(): return next(iter([])) # ooops
...
>>> for foo in leak(): print("Hello")
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in leak
StopIteration
>>> for foo in "test": print(leak())
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in leak
StopIteration

They don't prematurely end the loop because they're not happening in
places where that's the API you're working with.

The only times you should need to think about StopIteration are
calling next(), and implementing __next__.

ChrisA
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/EBI3YBM3TV72PBWPBAHWOJLB4PNNRTHM/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to