pythonfor-loopiteratorpep

Is calling next(iter) inside for i in iter supported in python?


Question: Python list iterator behavior and next(iterator) documents the fact that calling

for i in iter:
    ...
    next(iter)

Has the effect of skipping ahead in the for loop. Is this defined behavior that I can rely on (e.g. in some PEP) or merely an accident of implementation that could change without warning?


Solution

  • It depends on what iter is. If it’s an iterator created with a call of iter() on an iterable, then yes, calling next(iter) will also advance the iterator:

    >>> it = iter(range(4))
    >>> for x in it:
            print(x)
            _ = next(it)
    
    0
    2
    

    It’s important that it is an iterator (instead of just any iterable) since the for loop will internally also call iter() on that object. Since iterators (usually) return themselves when iter(some_iterator) is called, this works fine.

    Is this a good idea though? Generally not since it can easily break:

    >>> it = iter(range(3))
    >>> for x in it:
            print(x)
            _ = next(it)
    
    0
    2
    Traceback (most recent call last):
      File "<pyshell#21>", line 3, in <module>
        _ = next(it)
    StopIteration
    

    You would have to add exception handling for the StopIteration exception manually inside the loop; and that gets messy very easily. Of course you could also use a default value, e.g. next(it, None) instead, but that becomes confusing quickly, especially when you’re not actually using the value for anything.

    This whole concept also breaks as soon as someone later decides to not use an iterator but some other iterable in the for loop (for example because they are refactoring the code to use a list there, and then everything breaks).

    You should attempt to let the for loop be the only one that consumes the iterator. Change your logic so that you can easily determine whether or not an iteration should be skipped: If you can, filter the iterable first, before starting the loop. Otherwise, use conditionals to skip iterations within the loop (using continue):

    >>> it = filter(lambda x: x % 2 == 0, range(3))
    >>> for x in it:
            print(x)
    
    0
    2
    
    >>> it = range(3) # no need for an iterator here
    >>> for x in it:
            if x % 2 == 1:
                continue
            print(x)
    
    0
    2