将字典的键值对赋值用作 for 循环中的迭代器

Using key value pair assignments for a dictionary as an iterator in a for loop

我最近在 Python 中发现了这个很酷的 hack。

这个:

d = {}
for i, d[i] in enumerate('abc'):
    pass

>>> d
{0: 'a', 1: 'b', 2: 'c'}
>>> 

这会将键值对分配给迭代器中的空字典。

我想知道 Cython 后端是如何解析这个的,我的期望是它是通过解包赋值来解析的。但是很高兴知道这个的实际 Cython 实现,以及是否推荐这样做?

我知道我可以简单地做到:

d = {}
for i, v in enumerate('abc'):
    d[i] = v

但是上面的酷 hack 可以用更短的代码来做到这一点,但我不确定它在 Python 中是否被认为是好的做法。

我从没见过有人用这个...

您不必阅读 CPython 代码,因为行为已在 Python 文档中定义。

如果您阅读 for statement 的文档,for 语句中的目标列表使用标准赋值规则:

Each item in turn is assigned to the target list using the standard rules for assignments (see Assignment statements)

并且如果您阅读 assignment statements 的规则,您会看到赋值的目标列表中的每一项都是按从左到右的顺序分配给的:

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

所以在你的 for 循环的第一次迭代中,生成了一个元组 0, 'a'

for i, d[i] in enumerate('abc')

执行等价于以下的赋值语句:

i, d[i] = 0, 'a'

首先将 0 分配给 i,因为它在左侧,然后将 'a' 分配给 d[i],计算结果为 d[0],有效地使d[0] = 'a'.

其余迭代也是如此。

@blhsing 已经解释过这是普通的从左到右的赋值。我有时会这样做和类似的变化,我想添加 why:

  • 简洁。它稍微短一些,即使你的额外变量只有一个字母。
  • 速度。它避免了无意义的变量存储和加载。
  • 清洁度。 不会用无意义的变量污染命名空间。
  • 懒惰。不想去想变量名。 That's hard.
  • 教育。我喜欢指出人们不熟悉的事情:-)
  • 娱乐。 与上一点相关-我喜欢迷惑人:-D

所以要回答你的 “推荐与否?” 以及它是否 “被认为是好的做法”:我会说是的,我认为它非常好,甚至有优势。我看到的唯一潜在缺点是有些人可能会抱怨它纯粹是因为他们不熟悉它并且不喜欢陌生的东西。

关于速度方面:如下图dis.dis所示,除了额外变量v.

的额外STORE和LOAD之外,两个版本的一切都是一样的
dis.dis('''                       |  dis.dis('''
d = {}                            |  d = {}
for i, d[i] in enumerate('abc'):  |  for i, v in enumerate('abc'):
    pass                          |      d[i] = v
''')                              |  ''')
----------------------------------+-----------------------------------
 0 BUILD_MAP        0             |   0 BUILD_MAP        0               
 2 STORE_NAME       0 (d)         |   2 STORE_NAME       0 (d)           
 4 LOAD_NAME        1 (enumerate) |   4 LOAD_NAME        1 (enumerate)   
 6 LOAD_CONST       0 ('abc')     |   6 LOAD_CONST       0 ('abc')       
 8 CALL_FUNCTION    1             |   8 CALL_FUNCTION    1               
10 GET_ITER                       |  10 GET_ITER                         
12 FOR_ITER        12 (to 26)     |  12 FOR_ITER        16 (to 30)       
14 UNPACK_SEQUENCE  2             |  14 UNPACK_SEQUENCE  2               
16 STORE_NAME       2 (i)         |  16 STORE_NAME       2 (i)      
                                  |  18 STORE_NAME       3 (v)
                                  |  20 LOAD_NAME        3 (v)
18 LOAD_NAME        0 (d)         |  22 LOAD_NAME        0 (d)           
20 LOAD_NAME        2 (i)         |  24 LOAD_NAME        2 (i)           
22 STORE_SUBSCR                   |  26 STORE_SUBSCR                     
24 JUMP_ABSOLUTE   12             |  28 JUMP_ABSOLUTE   12              
26 LOAD_CONST       1 (None)      |  30 LOAD_CONST       1 (None)       
28 RETURN_VALUE                   |  32 RETURN_VALUE