How is the following construct accepted in Python:
l = [1, 2, 3, 4]
for i, l[i] in enumerate(l[:]):
print(l[i])
There seem to be no complaints and it happily prints out 1 2 3 4
. How is this allowed and what exactly does it do?
How is the following construct accepted in Python:
l = [1, 2, 3, 4]
for i, l[i] in enumerate(l[:]):
print(l[i])
There seem to be no complaints and it happily prints out 1 2 3 4
. How is this allowed and what exactly does it do?
The syntax rule for for
loops allows the iteration variables to be any of those specified in target_list
:
for_stmt ::= "for" target_list "in" expression_list ":" suite
["else" ":" suite]
where target_list
allows for the following constructs:
target_list ::= target ("," target)* [","]
target ::= identifier
| "(" [target_list] ")"
| "[" [target_list] "]"
| attributeref
| subscription
| slicing
| "*" target
This means you can also do other wacky things like assign to slices:
for l[::-1] in [l, l, l]: pass
or, subscriptions:
class Foo: a = 20
for Foo.a in range(2): pass
but I really have no idea why you'd want to do so.
This is a by-product of for-loop
s essentially performing an assignment statement for every iteration, as stated in the reference:
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.
So what the loop does is, it gets the iterator from the expression_list
and performs an assignment to each of the values in the target_list
. Essentially equivalent to the following while
loop:
it = enumerate(l[:])
while True:
try:
i, l[i] = next(it)
print(l[i])
except StopIteration:
break
dis
can also show this behavior manifesting on the byte-code level. Using a slightly simplified version:
def _():
for i, l[i] in enumerate(l[:]):
pass
you'd get an output of:
dis(_)
2 0 SETUP_LOOP 40 (to 43)
3 LOAD_GLOBAL 0 (enumerate)
6 LOAD_GLOBAL 1 (l)
9 LOAD_CONST 0 (None)
12 LOAD_CONST 0 (None)
15 BUILD_SLICE 2
18 BINARY_SUBSCR
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 GET_ITER
>> 23 FOR_ITER 16 (to 42)
26 UNPACK_SEQUENCE 2
29 STORE_FAST 0 (i)
32 LOAD_GLOBAL 1 (l)
35 LOAD_FAST 0 (i)
38 STORE_SUBSCR
3 39 JUMP_ABSOLUTE 23
>> 42 POP_BLOCK
>> 43 LOAD_CONST 0 (None)
46 RETURN_VALUE
where the relevant assignment is performed right after the the FOR_ITER
command:
26 UNPACK_SEQUENCE 2
29 STORE_FAST 0 (i)
32 LOAD_GLOBAL 1 (l)
35 LOAD_FAST 0 (i)
38 STORE_SUBSCR
unpacks the sequence and assigns it to i
and l[i]
.
If you also disassemble dis('i, l[i] = (1, 2)')
you'll see that if you ignore the initial loading of the tuple (1, 2)
and the returning of the value, the operations are exactly the same.