2

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?

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253

1 Answers1

2

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-loops 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.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • Isn't the issue here the sequence of interpreted assignment and therefore binding creation? Which makes the while example in itself sort of non-explanatory? – pvg Feb 26 '17 at 14:52
  • @pvg no no the issue that I was trying to highlight is the fact that there's an assignment going on ergo anything in `target_list` is allowed as the iteration variable for a `for-loop`. The `while` was written to show a friendly form of the sequence of commands executed and how it contains a hidden assignment. – Dimitris Fasarakis Hilliard Feb 26 '17 at 14:58
  • 1
    Ah, you're right, I see what you're saying. I'm obviously suffering from python binding- and scope- Stockholm syndrome. – pvg Feb 26 '17 at 15:07
  • @pvg hah, then you can check out [this nice article](http://eli.thegreenplace.net/2015/the-scope-of-index-variables-in-pythons-for-loops/) that explains the scoping situation in `for-loop`s nicely :-) – Dimitris Fasarakis Hilliard Feb 26 '17 at 15:11