【问题标题】:Using key value pair assignments for a dictionary as an iterator in a for loop将字典的键值对分配用作 for 循环中的迭代器
【发布时间】:2021-11-12 02:57:42
【问题描述】:

我最近在 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 中是否被认为是好的做法。

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

【问题讨论】:

  • 说到更短的代码d = dict(enumerate('abc')) 要好得多。但我尊重你的代码 sn-p 是如何完成的好奇心。
  • 哦,快。我什至没有检查。因为这太明显了:D
  • 其实第一个版本比第二个长:D
  • 你的意思是在 cpython 中?
  • @buran 在这个特殊的用法中,是的。在其他情况下,它有一些好处,正如我现在在回答中所描述的那样。

标签: python python-3.x dictionary for-loop enumerate


【解决方案1】:

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

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

使用标准将每个项目依次分配到目标列表 分配规则(见Assignment statements

如果您阅读assignment statements 的规则,您会看到分配的目标列表中的每个项目都按从左到右的顺序分配:

赋值语句计算表达式列表(请记住 这可以是单个表达式或逗号分隔的列表,后者 产生一个元组)并将单个结果对象分配给每个 目标列表,从左到右。

所以在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'

其余的迭代也是如此。

【讨论】:

    【解决方案2】:

    @blhsing 已经解释过这是普通的从左到右的赋值。我有时会这样做和类似的变体,我想补充一下为什么

    • 简洁。它稍微短一些,即使你的额外变量只有一个字母。
    • 速度。它避免了无意义的变量存储和加载。
    • 清洁度。不会用无意义的变量污染命名空间。
    • 懒惰。不想考虑变量名。 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
    

    【讨论】:

    • 哇!感谢您的明确输入!这是我所期望的,每个人都熟悉的常规人必须将迭代器v 分配给该值,然后将其分配给字典,而每个人都不熟悉的人不需要那个额外的step,直接存入字典,不需要存额外的迭代器v。是的!我也喜欢用别人不熟悉的东西给别人惊喜,就像你一样。
    • 这值得更多的支持,这很清楚:) blhsing 也提供了非常好的文档参考和解释。他先发了,所以我会继续接受他的:)
    • @U12-Forward 好吧,我现在确实获得了另一个投票。我猜有人不喜欢我在“唯一潜在的缺点”句子中描述它们的准确度:-)
    • @U12-Forward 是的,这很清楚(来自您的 cmets 和早期的投票,大概来自您)。我确实认为这是来自不喜欢这样的循环分配(尽管他们没有否决这个问题)或者不喜欢我提倡它们的人。
    • 呃...这很奇怪
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 1970-01-01
    • 2020-04-08
    • 1970-01-01
    • 2019-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多