我遇到了以下有趣的想法:
假设您有如下列表:
my_list = [['captain1', 'foo1', 'bar1', 'foobar1'], ['captain2', 'foo2', 'bar2', 'foobar2'], ...]
您想用0
-index元素作为键来创建dict。一种简便的方法是:
看起来,pop
在赋值给list x
之前,这就是为什么'captain'
没有出现在值中(它已经弹出)
现在让我们更进一步,试着得到这样的结构:
# {'captain1': {'column1': 'foo1', 'column2': 'bar1', 'column3': 'foobar1'}, ...}
对于这项任务,我写了以下内容:
my_headers = ['column1', 'column2', 'column3']
my_dict = {x.pop(0): {k: v for k, v in zip(my_headers, x)} for x in my_list}
但这会带来:
# {'captain1': {'col3': 'bar1', 'col1': 'captain1', 'col2': 'foo1'}, 'captain2': {'col3': 'bar2', 'col1': 'captain2', 'col2': 'foo2'}}
因此,本例中的pop
发生在构造内部字典之后(或者至少在{
怎么可能呢?这是怎么回事?在
问题不在于如何去做,而在于为什么会看到这种行为。在
我使用的是Python版本3.5.1。
不,事情发生的顺序无关紧要。您正在改变列表,以便在使用pop之后看到修改后的列表。请注意,一般情况下,您可能不想这样做,因为这样会破坏原始列表。即使这不重要,这一次也会成为未来不小心的陷阱。在
在这两种情况下,首先计算值侧,然后计算相应的键。只是第一种情况无关紧要,而在第二种情况下却如此。在
您可以很容易地看到:
请注意,您不应该编写依赖于首先评估的值的代码:在将来的版本中,行为可能会发生变化(在某些地方,在python3.5和更高版本中,虽然事实上情况并非如此)。在
一种更简单的方法可以避免改变原始数据结构:
^{2}$或者你的第二个例子:
回答注释:zip使用原始的
x
,因为它是在pop
之前计算的,但是它使用列表的内容来构造一个新的列表,这样以后对列表的任何更改都不会出现在结果中。第一个理解也使用原始的x
作为值,但是它会对列表进行变异,这样值仍然可以看到原始列表,因此也会发生变异。在正如我在评论中所说,这是因为在字典理解中,python首先计算值。作为一种更具python风格的方法,您可以使用解压变量来完成此任务,而不是在每次迭代中从列表中弹出:
关于python在计算字典理解中的键和值时的奇怪行为,经过一些实验,我意识到这种行为在某种程度上是合理的,而不是一个bug。在
我将从以下几个方面来减少我的印象:
在赋值表达式中,python首先计算右侧。 来自文件:
字典理解是一个表达式,它将从左到右进行计算,但是由于在python的翻译下有一个赋值。 首先计算右边的had值。在
例如以下理解:
{b.pop(0): b.pop(0) for _ in range(1)}
与以下代码段等效:^{2}$
以下是一些示例:
关于事实之间的差异(或者更确切地说是抽象),字典理解是表达式,应该从左到右进行计算(基于python文档) 根据观察到的行为,我认为这实际上是python文档的问题和不成熟,而不是python代码中的bug。因为毫无例外地拥有一致的文档而更改功能是完全不合理的。在
注意:从python3.8和PEP 572开始,这一点已经改变,并且首先计算密钥。在
tl;dr Until python3.7:尽管Python首先计算值(表达式的右侧),但根据the reference manual、the grammar和PEP on dict comprehensions,这在(C)Python中确实是一个bug。在
虽然这是以前的fixed for dictionary displays,其中的值在键之前被再次评估,补丁没有被修改为包含dict理解。This requirement was also mentioned by one of the core-devs in a mailing list thread discussing this same subject。在
根据参考手册,Python从左到右计算表达式和从右到左的赋值;dict理解实际上是包含表达式的表达式,不是赋值*:
其中,根据相应的rule of the ^{} ,人们期望
expr1: expr2
的计算方式与它在显示中所做的类似。因此,这两个表达式都应该遵循定义的顺序,expr1
应该在expr2
之前求值(而且,如果expr2
包含自己的表达式,它们也应该从左到右进行计算。)dict comps上的PEP还指出以下内容在语义上应该是等价的:
元组
(i, chr(65+i))
是否按预期从左到右求值。在当然,将其更改为根据表达式规则的行为将在
dict
s的创建中造成不一致。字典理解和带有赋值的for循环会导致不同的求值顺序,但这没关系,因为它只是遵循规则。在虽然这不是一个主要的问题,但它应该被修正(无论是评估规则,还是文档),以消除这种情况的歧义。在
*在内部,这确实会导致对dictionary对象的赋值,但这不应破坏表达式应该具有的行为。用户对表达式的行为有期望,如参考手册中所述。在
正如其他回答者所指出的那样,由于您在其中一个表达式中执行了一个变异操作,所以您会丢弃关于首先计算的内容的任何信息;使用
print
调用,就像邓肯所做的那样,可以揭示出所做的事情。在帮助显示差异的函数:
^{2}$(固定)字典显示:
(奇怪)字典理解:
是的,这特别适用于
C
Python。我不知道其他实现如何评估这个特定的情况(尽管它们都应该符合Python参考手册)挖掘源代码总是很好的(您还可以找到描述行为的隐藏注释),所以让我们来看看文件^{} 的
compiler_sync_comprehension_generator
:这似乎是一个足够好的理由,如果这样判断的话,应该被归类为文档错误。在
在我做的一个快速测试中,切换这些语句(} (用于dict comp):
VISIT(c, expr, elt);
首先访问),同时切换相应的order in ^{评估中的结果基于文档,在值之前评估密钥。(对于异步版本,这是另一个需要的开关。)
如果有人回复我,我会对这个问题发表评论并更新。在跟踪器上创建了Issue 29652 -- Fix evaluation order of keys/values in dict comprehensions。当问题有进展时会更新问题。在
相关问题 更多 >
编程相关推荐