为什么在for循环中允许任意的目标expression式?

我不小心写了这样的代码:

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

但令我惊讶的是,这不是一个语法错误。 而是打印{'c': 'd', 'z': 42}

我的猜测是,代码字面上翻译成类似于:

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

但是…为什么这是语言所允许的呢? 我期望只有单一的标识符和标识符的元组应该被允许在目标的目标expression式中 。 有没有什么情况下,这实际上是有用的,不只是一个怪异的陷阱?

for循环遵循标准的赋值规则,所以对于香草任务的LHS的工作应该与for

每个项目依次使用标准分配规则分配给目标列表

for构造只是简单地召集分配给目标的底层机制,在你的示例代码中是STORE_SUBSCR

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

但令我惊讶的是,这不是一个语法错误

显然,无论在以下常规任务中如何工作:

全切片分配

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

列表订阅

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

字典订阅等将是有效的候选目标,唯一的例外是链接分配 ; 尽pipe我偷偷地以为可以用一些蹩脚的语法来进行链接。


我希望只有单一的标识符和元组的标识符

我不能把字典密钥用作目标。 此外,在循环体中执行字典键赋值比在for子句中使用它更可读。

然而,在常规任务中非常有用的扩展解包(Python 3)在for循环中也同样方便:

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

也调用了相应的分配给不同目标的机制。 多个STORE_NAME

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

去certificate一个for只是简单的赋值语句连续执行。

下面的代码是有意义的,对吧?

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

for循环将迭代foo列表,并依次将每个对象分配给当前名称空间中的名称x 。 结果将是一个迭代和一个42的单个打印。

在你的代码中代替x ,你有k['z']k['z']是一个有效的存储名称。 像我的例子中的x ,它不存在。 实际上,在全局命名空间中是kz 。 循环创buildkzk['z']并将其在foofind的值赋给它,方法与创buildx相同,并在我的示例中为其赋值。 如果你有更多的价值观…

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

会导致:

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

你写了完全有效的意外代码。 这甚至不是奇怪的代码。 您通常不会将字典条目视为variables。

即使代码不奇怪,如何允许这样的任务是有用的?

 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 

可能不是最好的办法,但是把两个等长的列表放在一起。 我相信还有更多有经验的程序员在循环中使用字典键分配的其他东西。 也许…

每个名字只是一个字典键*。

 for x in blah: 

正是

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

*(尽pipe该字典不需要作为一个实际的dict对象来实现,但是在一些优化的情况下,比如在函数范围中)。

有没有什么情况下这实际上是有用的?

确实。 曾经想摆脱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)