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

jtb*_*des 58 python for-loop language-lawyer

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

foo = [42]
k = {'c': 'd'}

for k['z'] in foo:  # Huh??
    print k
Run Code Online (Sandbox Code Playgroud)

但令我惊讶的是,这不是语法错误.相反,它打印{'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
Run Code Online (Sandbox Code Playgroud)

但是......为什么语言允许这样做?我希望在for-stmt的目标表达式中只允许使用单个标识符和标识符元组.是否存在实际有用的情况,而不仅仅是一个奇怪的问题?

Mos*_*oye 31

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
Run Code Online (Sandbox Code Playgroud)

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

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

完整切片分配:

>>> for [][:] in []:
...    pass
... 
>>>
Run Code Online (Sandbox Code Playgroud)

列表订阅

>>> for [2][0] in [42]:
...    pass
... 
>>> 
Run Code Online (Sandbox Code Playgroud)

字典订阅等将是有效的候选目标,唯一的例外是链式分配 ; 虽然,我暗自认为可以编写一些脏语法来执行链接.


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

我想不出一个字典键作为目标的好用例.此外,在循环体中进行字典键赋值比在for子句中将其用作目标更具可读性.

但是,在常规赋值中非常有用的扩展解包(Python 3)在for循环中也同样方便:

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

这里也分配了相应的分配给不同目标的机制; 多个STORE_NAMEs:

>>> 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
Run Code Online (Sandbox Code Playgroud)

去表明a for几乎不是简单的赋值语句连续执行.

  • 鉴于链式赋值将是`(x =(y = v))`而不是`(x = y)= v`,我怀疑你是否可以提出语法来使它工作 (2认同)

Ala*_*ard 27

以下代码是有道理的,对吗?

foo = [42]
for x in foo:
    print x
Run Code Online (Sandbox Code Playgroud)

for循环将遍历列表foo并且每个对象分配到的名称x依次以当前的命名空间.结果将是单次迭代和单次打印42.

代替x你的代码,你有k['z']. k['z']是有效的存储名称.就像x在我的例子中,它还不存在.实际上,它k.z在全局命名空间中.循环以与创建它相同的方式创建k.zk['z']分配它找到的值,并在我的示例中为其赋值.如果你在foo中有更多的价值......foox

foo = [42, 51, "bill", "ted"]
k = {'c': 'd'}
for k['z'] in foo:
    print k
Run Code Online (Sandbox Code Playgroud)

会导致:

{'c': 'd', 'z': 42}
{'c': 'd', 'z': 51}
{'c': 'd', 'z': 'bill'}
{'c': 'd', 'z': 'ted'}
Run Code Online (Sandbox Code Playgroud)

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

即使代码不奇怪,如何允许这样的赋值有用?

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
Run Code Online (Sandbox Code Playgroud)

可能不是最好的方法,但将两个相等长度的列表放在一个字典中.我确信还有其他更有经验的程序员在for循环中使用字典键分配.也许...

  • 这种解释使其比其他人更清楚.干得好! (2认同)
  • 虽然`dict(zip(key_list,loc_list))`可能比滥用`for`循环更容易理解. (2认同)

Vek*_*eky 7

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

for x in blah:
Run Code Online (Sandbox Code Playgroud)

确切地说

for vars()['x'] in blah:
Run Code Online (Sandbox Code Playgroud)

*(尽管dict在某些优化的情况下,例如在函数范围内,该字典不需要实现为实际对象).

  • 是的,公平地说,作为一个例子,这是完全有道理的.我刚刚看到太多人因为修改`locals()`(或`vars()`)返回的字典在函数内部不起作用而感到困惑,所以我更喜欢尽可能明确地说明它. (2认同)

Uri*_*iel 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)
Run Code Online (Sandbox Code Playgroud)