pythoniterator

Which is a better way to move an iterator one step forward: `for x in iterator: break` or `x = next(iterator, None)`?


While working on a learning task involving overlapping n-wise windows from an input iterable - similar to what itertools.pairwise does - I came across code like this:

def f(seq):
    it = iter(seq)
    for x in it: break
    for y in it:
        yield x, y
        x = y

The line for x in it: break was used instead of x = next(it, None) to avoid assigning x at all if seq is empty, which would be the case with the next() call.

I'm not familiar with CPython internals or low-level details, so I can't say whether this is a justified choice. Could someone help me understand the pros and cons of both approaches for advancing an iterator by one item before a loop?


Solution

  • next() is more readable, and I think inarguably so. But the for trick generally runs faster, and that's primarily what drives most uses I've seen. Although speed wouldn't matter in the "one-and-done" context you're showing.

    The line for x in it: break was used instead of x = next(it, None) to avoid assigning x at all if seq is empty,

    It's more because x = next(it) would raise the StopIteration exception, and the coder didn't want to bother with try/except to deal with that. Note that x is assigned to in all cases when x = next(it, None) is used. If the iterator is exhausted, next(it, None) returns the specified default (None), and x is bound to that None.

    A slight extension is also common:

        for x in it:
            break
        else:
            # No break, so StopIteration must have been raised
    

    That variation is arguably more readable than:

        if (x := next(it, None)) is None:
            # StopIteration must have been raised
    

    and then you have to worry too about whether a non-empty iterator may yield None.

        try:
            x = next(it)
        except StopIteration:
            pass
    

    is straightforward and clear, but somehow "feels like" overkill. It's wordy. Which is why next() has an optional argument to specify a default.

    Bottom line: use whichever you like best :-) But you'll see all ways of doing this in other code, and should learn to live with them. Nobody is going to change what they like best ;-)