Reassigning value of iterator while iterating through it

Viewed 337

First of all a disclaimer:

I don't want to use a code like this, I am aware it is a bad practice. As well I am not interested in tips on how to improve it, to make it right. What interests me is a theory.

How come code like this works in python 3.6:

ls = range(5)
for elem_a in ls:

    ls = range(5, 10)
    for elem_b in ls:
        print(elem_a, elem_b)

I am reassigning the value of ls while iterating through it. Is the value of ls in the first iteration stored in memory during the first execution of for elem_a in ls?

1 Answers

Reassigning the variable you're looping over has no effect because the variable isn't re-evaluated for every iteration. In fact, the loop internally loops over an iterator, not over your range object.

Basically if you have a loop like this:

seq = range(5)
for elem in seq:
    seq = something_else

Python rewrites it to something like this:

seq = range(5)

loop_iter = iter(seq)  # obtain an iterator
while True:
    try:
        elem = next(loop_iter)  # get the next element from the iterator
    except StopIteration:
        break  # the iterator is exhausted, end the loop

    # execute the loop body
    seq = something_else

The crucial aspect of this is that the loop has its own reference to iter(seq) stored in loop_iter, so naturally reassigning seq has no effect on the loop.

All of this is explained in the compound statement documentation:

for_stmt ::=  "for" target_list "in" expression_list ":" suite
              ["else" ":" suite]

The expression list is evaluated once; it should yield an iterable object. An iterator is created for the result of the expression_list. The suite is then executed once for each item provided by the iterator, in the order returned by the iterator.

Related