Why are arbitrary target expressions allowed for for-loops?

I accidentally wrote this code:

foo = [42] k = {'c': 'd'} for k['z'] in foo: # Huh?? print k 

But, to my surprise, this was not a syntax error. Instead, it prints {'c': 'd', 'z': 42} .

My assumption is that the code translates literally into something like:

 i = iter(foo) while True: try: k['z'] = i.next() # literally translated to assignment; modifies k! print k except StopIteration: break 

But ... why is language allowed? I would expect that for-stmt target expression only single identifiers and identifier tuples should be allowed . Is there some kind of situation in which it is really useful, and not just strange production?

+57
python language-lawyer for-loop
Jun 01 '17 at 22:20
source share
4 answers

The for loop follows standard assignment rules, so what works on the vanilla name LHS should work with for :

Each item is in turn assigned to the target list using the standard assignment rule.

The for construct simply invokes the fundamental target assignment mechanism, which in the case of your STORE_SUBSCR sample code:

 >>> foo = [42] >>> k = {'c': 'd'} >>> dis.dis('for k["e"] in foo: pass') 1 0 SETUP_LOOP 16 (to 18) 2 LOAD_NAME 0 (foo) 4 GET_ITER >> 6 FOR_ITER 8 (to 16) 8 LOAD_NAME 1 (k) 10 LOAD_CONST 0 ('e') 12 STORE_SUBSCR <-------------------- 14 JUMP_ABSOLUTE 6 >> 16 POP_BLOCK >> 18 LOAD_CONST 1 (None) 20 RETURN_VALUE 

But, to my surprise, it was not a syntax error

Apparently everything that works on a regular basis, such as:

full purpose of slice :

 >>> for [][:] in []: ... pass ... >>> 

list subscription

 >>> for [2][0] in [42]: ... pass ... >>> 

dictionary subscription, etc. will be valid target candidates, with a single exception being a tied destination ; although, I secretly think that you can prepare some kind of dirty syntax to execute the chain.




I would expect only single identifiers and identifier tuples

I cannot come up with a good use case for a dictionary key as a goal. In addition, it is more readable to perform the assignment of a dictionary key in the body of a loop than to use it as a target in a for clause.

However, advanced unpacking (Python 3), which is very useful with regular assignments, is equally useful in a for loop:

 >>> lst = [[1, '', '', 3], [3, '', '', 6]] >>> for x, *y, z in lst: ... print(x,y,z) ... 1 ['', ''] 3 3 ['', ''] 6 

The corresponding mechanism of appointment for different goals of the target is also called; multiple STORE_NAME s:

 >>> dis.dis('for x, *y, z in lst: pass') 1 0 SETUP_LOOP 20 (to 22) 2 LOAD_NAME 0 (lst) 4 GET_ITER >> 6 FOR_ITER 12 (to 20) 8 EXTENDED_ARG 1 10 UNPACK_EX 257 12 STORE_NAME 1 (x) <----- 14 STORE_NAME 2 (y) <----- 16 STORE_NAME 3 (z) <----- 18 JUMP_ABSOLUTE 6 >> 20 POP_BLOCK >> 22 LOAD_CONST 0 (None) 24 RETURN_VALUE 

Shows that a for are just simple assignment statements executed in sequence.

+30
Jun 01 '17 at 22:27
source share

The following code makes sense, right?

 foo = [42] for x in foo: print x 

The for loop will iterate over the foo list and assign each object the name x in the current namespace in turn. The result will be a single iteration and one fingerprint 42 .

Instead of x in your code, you have k['z'] . k['z'] is a valid repository name. Like x in my example, it does not exist yet. This is essentially kz in the global namespace. The loop creates kz or k['z'] and assigns it the values ​​it found in foo , in the same way that it would create x and assign values ​​to it in my example. If you had more values ​​in foo ...

 foo = [42, 51, "bill", "ted"] k = {'c': 'd'} for k['z'] in foo: print k 

will result in:

 {'c': 'd', 'z': 42} {'c': 'd', 'z': 51} {'c': 'd', 'z': 'bill'} {'c': 'd', 'z': 'ted'} 

You wrote absolutely correct random code. This is not even a weird code. Usually you do not consider vocabulary entries as variables.

Even if the code is not strange, how can such an assignment be allowed?

 key_list = ['home', 'car', 'bike', 'locker'] loc_list = ['under couch', 'on counter', 'in garage', 'in locker'] chain = {} for index, chain[key_list[index]] in enumerate(loc_list): pass 

This is probably not the best way to do this, but along with two lists of the same length in a dictionary. I’m sure that there are other things that more experienced programmers have used to train as keywords. May be,...

+26
Jun 01 '17 at 23:10
source share

Each name is simply a dictionary key *.

 for x in blah: 

for sure

 for vars()['x'] in blah: 

* (although this dictionary should not be implemented as an actual dict object, in case of some optimizations, for example, in the field of functions).

+6
Jun 02 '17 at 3:20
source share

Is there any situation where this is really useful?

Really. Ever wanted to get rid of itertools.combinations ?

 def combinations (pool, repeat): def combinations_recurse (acc, pool, index = 0): if index < len(acc): for acc[index] in pool: yield from combinations_recurse(acc, pool, index + 1) else: yield acc yield from combinations_recurse([pool[0]] * repeat, pool) for comb in combinations([0, 1], 3): print(comb) 
+5
Jun 01 '17 at 22:49
source share



All Articles