On Sun, 10 Oct 2021 at 04:56, Steven D'Aprano <steve@pearwood.info> wrote:
On Fri, Oct 08, 2021 at 01:42:35PM -0700, Christopher Barker wrote:

> Anyway, I do see the benefit of adding first() to itertools -- there really
> is a key problem with:
>
> next(iter(an_iterable))
>
> for newbies -- you can get really really far in Python without ever having
> to call  either next() or iter(). Sure, if it's a recipe, people can use it
> without really understanding it, but  having an easy and obvious solution
> would be nice.

Yes? How does that make it a *problem*? I disagree strongly that needing
to learn a simple, basic technique is a problem to be solved.

The real problem is the fact that it raises the wrong kind of exception in the degenerate case of an empty iterable:

In [50]: next(iter([]))
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-50-bfed92c5b1cf> in <module>
----> 1 next(iter([]))

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.

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.

The correct usage of next/iter in most cases would be something like:

try:
    val = next(iter(obj))
except StopIteration:
    raise AnotherError

or perhaps

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

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.

--
Oscar