I have the following code:

``````a = [0,1,2,3]

for a[-1] in a:
print(a[-1])``````

The output is:

``````0
1
2
2``````

I’m confused about why a list index can be used as an indexing variable in a for loop.

List indexes such as `a[-1]` in the expression `for a[-1] in a` are valid as specified by the `for_stmt` (and specifically the `target_list`) grammar token, where `slicing` is a valid target for assignment.

“Huh? Assignment? What has that got to do with my output?”

Indeed, it has everything to do with the output and result. Let’s dive into the documentation for a `for-in` loop:

``for_stmt ::=  "for" target_list "in" expression_list ":" 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. Each item in turn is assigned to the target list using the standard rules for assignments (see Assignment statements), and then the suite is executed.

N.B. the suite refers to the statement(s) under the for-block, `print(a[-1])` in our particular case.

Let’s have a little fun and extend the print statement:

``````a = [0, 1, 2, 3]
for a[-1] in a:
print(a, a[-1])``````

This gives the following output:

``````[0, 1, 2, 0] 0    # a[-1] assigned 0
[0, 1, 2, 1] 1    # a[-1] assigned 1
[0, 1, 2, 2] 2    # a[-1] assigned 2
[0, 1, 2, 2] 2    # a[-1] assigned 2 (itself)``````

Here, `a[-1]` changes on each iteration and we see this change propagated to `a`. Again, this is possible due to `slicing` being a valid target.

A good argument made by Ev. Kounis regards the first sentence of the quoted doc above: “The expression list is evaluated once“. Does this not imply that the expression list is static and immutable, constant at `[0, 1, 2, 3]`? Shouldn’t `a[-1]` thus be assigned `3` at the final iteration?

No, [the expression list is] evaluated once to create an iterable object. But that iterable object still iterates over the original data, not a copy of it.

The following code demonstrates how an iterable `it` lazily yields elements of a list `x`.

``````x = [1, 2, 3, 4]
it = iter(x)
print(next(it))    # 1
print(next(it))    # 2
print(next(it))    # 3
x[-1] = 0
print(next(it))    # 0``````

(code inspired by Kounis’)

If evaluation was eager, we could expect `x[-1] = 0` to have zero effect on `it` and expect `4` to be printed. This is clearly not the case and goes to show that by the same principle, our `for`-loop lazily yields numbers from `a` following assignments to `a[-1]` on each iteration.