2017-06-01 18 views
57

我不小心写了一些这样的代码:为什么在for-loops中允许使用任意的目标表达式?

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 

但是......这是为什么允许的语言?我期望在for-stmt's target expression中只能使用单个标识符和标识符元组。有没有什么情况下,这实际上是有用的,而不仅仅是一个奇怪的陷阱?

+0

它不一定是有用的被允许。字典中的键值是一个有效的标识符。由于命名空间本身就是字典,所以改变它需要改变很多Python内部。并且解决一些只有在编写不会导致错误的意外代码时才成为问题的问题。 –

+0

@AlanLeuthard我明白你在说什么(并且你的回答很好地表达出来),但更确切的说,是一个['identifier'](https://docs.python.org/2/reference/lexical_analysis.html#语法 - 标记 - 标识符)只能是字母和数字的序列,即实际变量*名称*,而不是其他可分配的表达式,例如['subscription'](https://docs.python.org/2/参考/ expressions.html#语法-令牌订阅)。 – jtbandes

+0

是的。应该有说的目标,而不是标识符 –

回答

30

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

字典订阅等将是有效的候选目标,其中唯一的例外是一个链分配;尽管,我暗自认为可以用一些肮脏的语法来进行链接。


我希望只有一个标识符和标识符

我想不出很好的利用情况的字典键为目标的元组。此外,在循环体中执行字典键分配的可读性要高于在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 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 

正好说明一个for是连续执行勉强简单的赋值语句。

+2

由于链接赋值将是' (x =(y = v))'not'(x = y)= v',我怀疑你可以拿出语法来使它工作 – Bergi

+0

@Bergi是的,这是一个艰难的电话,但可以得到一些接近链接:'对于x,y in zip(* tee(iterable,no_of_targets)):...'。 OTOH,我认为'x'应该首先分配,而不是'y',这样'x = y = v'应该是'x = v; y = v' –

5

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

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

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

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

for循环会遍历列表foo和每个对象依次以当前名字空间分配给名称x。结果将是一次迭代和单个打印42

代替你的代码中的x,你有k['z']k['z']是有效的存储名称。在我的例子中,像x,它还不存在。实际上,它在全局命名空间中为k.z。该循环创建k.zk['z']并将它在foo中找到的值赋值给它,方法与创建x的方法相同,并在我的示例中将值赋给它。如果你曾在富更值...

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

你写的完全合法的意外代码。这甚至不是奇怪的代码。您通常不会将字典条目视为变量。

即使代码并不奇怪,如何让这样的任务有用呢?

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循环中使用了字典键分配。也许...

+2

这个解释比其他解释更清楚。干得好! – tpg2114

+1

尽管你的第一个代码示例并没有超出我在问题中的“猜测”范围,但这个解释非常明确,我相信这对未来的读者会有用......如果有人偶然再次写错了:) 谢谢! – jtbandes

+2

尽管'dict(zip(key_list,loc_list))'可能比这个'for'循环的滥用更容易理解。 – Graipher

6

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

for x in blah: 

恰恰是

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

*(尽管这字典不需要被实现为实际dict对象,在一些优化,的情况下,如在函数作用域)。

+0

这两个代码示例并不完全相同,因为第一个代码示例在函数内部工作,而第二个代码示例不是,但您的主要观点在此。 –

+0

也许我的英语是问题。我没有说,也没有打算说,那些代码样本完全一样。 (当然,因为你可能已经看到我在下面包括了脚注)。我的意思是,“赋予名称的精确语义只是某个字典上的一个集合”,尽管该字典不需要作为真正的Python字典对象。 – Veky

+2

是的,很公平,作为一个例子,这是完全合理的。我刚才看到太多的人因为修改由locals()(或'vars()')返回的字典在函数内部不起作用而感到困惑,所以我更愿意尽可能明确地表达它。 –

相关问题