我遇到了以下有趣的 struct :

假设您有如下列表:

my_list = [['captain1', 'foo1', 'bar1', 'foobar1'], ['captain2', 'foo2', 'bar2', 'foobar2'], ...]

你想用0个索引元素作为键来创建一个dict.一个简便的方法是:

my_dict = {x.pop(0): x for x in my_list}
# {'captain1': ['foo1', 'bar1', 'foobar1'], ...}

看起来,pop先于列表x的赋值,这就是'captain'没有出现在值中的原因(它已经被弹出)

现在,让我们更进一步,try 得到如下 struct :

# {'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发生在内部字典构建之后(或者至少在zip之后).

怎么会这样?这是怎么回事?

问题不在于如何做到这一点,而在于为什么会出现这种行为.

我使用的是Python版本3.5.1.

推荐答案

Note:从Python3.8和PEP 572开始,这一点已经改变,首先计算键.


tl;dr Until Python 3.7:尽管Python does首先计算值(表达式的右侧),但在(C)Python中,this does appear to be a bug根据the reference manualthe grammar以及PEP on dict comprehensions进行计算.

虽然这之前是fixed for dictionary displays,在键之前再次判断值,the patch wasn't amended包括dict理解.This requirement was also mentioned by one of the core-devs in a mailing list thread discussing this same subject

根据参考手册,Python判断expressions from left to rightassignments from right to left;口述理解实际上是一个包含表达式的表达式,not an assignment*:

{expr1: expr2 for ...}

其中,根据相应的rule of the grammar,人们期望expr1: expr2的判断与它在显示器中的判断类似.因此,两个表达式都应该遵循定义的顺序,expr1应该在expr2之前求值(如果expr2包含自己的表达式,它们也应该从左到右求值)

dict comps上的PEP还指出,以下内容在语义上应是等效的:

dict理解的语义实际上可以在

>>> dict([(i, chr(65+i)) for i in range(4)])

在语义上等同于:

>>> {i : chr(65+i) for i in range(4)}

元组(i, chr(65+i))按预期从左到右计算.

当然,将其更改为根据表达式规则进行操作会在创建dict时产生不一致性.字典理解和带有赋值的for循环会导致不同的求值顺序,但这没关系,因为它只是遵循规则.

虽然这不是一个主要问题,但应该修正(判断规则或文档)以消除歧义.

*Internally,这确实会导致对dictionary对象的赋值,但这不应 destruct 表达式应有的行为.用户对表达式的行为有期望,如参考手册所述.


正如其他回答者所指出的那样,由于你在其中一个表达式中执行了一个变异操作,你就丢弃了任何关于先计算什么的信息;像邓肯一样,使用print个电话,就可以清楚地知道该怎么做.

有助于显示差异的功能:

def printer(val):
    print(val, end=' ')
    return val

(固定)字典显示:

>>> d = {printer(0): printer(1), printer(2): printer(3)}
0 1 2 3

(奇数)字典理解:

>>> t = (0, 1), (2, 3)
>>> d = {printer(i):printer(j) for i,j in t}
1 0 3 2

是的,这特别适用于CPython.我不知道其他实现如何判断这个特定 case (尽管它们都应该符合Python参考手册)

挖掘源代码总是很好的(你也可以找到描述行为的隐藏注释),所以让我们看看文件compile.ccompiler_sync_comprehension_generator:

case COMP_DICTCOMP:
    /* With 'd[k] = v', v is evaluated before k, so we do
       the same. */
    VISIT(c, expr, val);
    VISIT(c, expr, elt);
    ADDOP_I(c, MAP_ADD, gen_index + 1);
    break;

这似乎是一个足够好的理由,如果这样判断的话,应该被归类为文档错误.

在我做的一个快速测试中,切换这些语句(首先访问VISIT(c, expr, elt);个语句),同时也切换相应的order in MAP_ADD个语句(用于dict comps):

TARGET(MAP_ADD) {
    PyObject *value = TOP();   # was key 
    PyObject *key = SECOND();  # was value
    PyObject *map;
    int err;

基于文档的判断结果,在值之前判断键.(不是异步版本,这是另一个switch .)


我会对这个问题发表 comments ,并在有人回复我时更新

在追踪器上创建了Issue 29652 -- Fix evaluation order of keys/values in dict comprehensions个.将在取得进展时更新问题.

Python-3.x相关问答推荐

TypeError:&Quot;Value&Quot;参数必须是标量、Dict或Series,但您传递了&Quot;Index&Quot;

我想判断df_entry_log[AM_PM],并根据测试填充列

在BaseHTTPRequestHandler中填充和返回列表

如何从包含SPAN文本的标记中获取链接

如何使用正则表达式通过反向搜索从链接中获取特定文本

如何查找以开头并替换的字符串

将水平堆叠的数据排列成垂直

如何在 histplot 中标记核密度估计

为什么 return node.next 会返回整个链表?

列出相同索引的Pandas

python 3.10.5 中可能存在的错误. id 函数工作不明确

在python中将字符串写入文本文件

用于 BIG 数组计算的多处理池映射比预期的要慢

FastAPI - 调用 API 时设置 response_model_exclude

将变量传递给 Google Cloud 函数

Linux Mint 上的 Python3 错误没有名为蓝牙的模块

Python configparser 不会接受没有值的键

计算两个文件的行差异的最有效方法是什么?

无法解码 Python Web 请求

有效地判断一个元素是否在列表中至少出现 n 次