Note:从 Python 3.8 开始以及PEP 572 https://www.python.org/dev/peps/pep-0572/,这已更改,并且首先评估密钥。
tl;dr 直到 Python 3.7: 即使是Pythondoes首先计算值(表达式的右侧)这似乎确实是一个错误在 (C)Python 中根据参考手册 https://docs.python.org/3/reference/expressions.html#evaluation-order and 语法 https://github.com/DimitrisJim/cpython/blob/79ab8be05fb4ffb5c258d2ca49be5fc2d4880431/Grammar/Grammar#L118和关于 dict 理解的 PEP https://www.python.org/dev/peps/pep-0274/#semantics.
虽然这是以前修复了字典显示的问题 http://bugs.python.org/issue11205在键之前再次评估值,补丁没有修改包括字典理解。一位核心开发人员在讨论同一主题的邮件列表主题中也提到了这一要求 https://mail.python.org/pipermail/python-dev/2012-November/122584.html.
According to the reference manual, Python evaluates expressions from left to right and assignments from right to left; a dict-comprehension is really an expression containing expressions, not an assignment*:
{expr1: expr2 for ...}
其中,根据相应的的规则grammar https://github.com/DimitrisJim/cpython/blob/79ab8be05fb4ffb5c258d2ca49be5fc2d4880431/Grammar/Grammar#L118人们会期望expr1: expr2
其评估方式与显示器中的评估方式类似。因此,两个表达式都应遵循定义的顺序,expr1
应先评估expr2
(而如果expr2
包含其自己的表达式,它们也应该从左到右计算。)
dict-comps 上的 PEP 还指出以下内容在语义上应该是等效的:
字典理解的语义实际上可以在
常用的 Python 2.2,通过将列表理解传递给内置函数
字典构造函数:
>>> 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, this does result in an assignment to a dictionary object but, this shouldn't break the behavior expressions should have. Users have expectations about how expressions should behave as stated in the reference manual.
正如其他回答者指出的那样,由于您在其中一个表达式中执行了变异操作,因此您会丢弃有关首先评估的内容的任何信息;使用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
是的,这特别适用于C
Python。我不知道其他实现如何评估这个特定情况(尽管它们都应该符合Python参考手册。)
挖掘源代码总是很好的(而且您还可以找到描述行为的隐藏注释),所以让我们看看compiler_sync_comprehension_generator
文件的compile.c https://github.com/python/cpython/blob/3cdbd68ce8230cff1afb67472b96fbfa7f047e32/Python/compile.c#L3752:
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);
首先被访问)同时也切换相应的订购MAP_ADD https://github.com/python/cpython/blob/e32ec9334b35f897ace8a05128838f92c5e0b2fb/Python/ceval.c#L2754(用于 dict-comps):
TARGET(MAP_ADD) {
PyObject *value = TOP(); # was key
PyObject *key = SECOND(); # was value
PyObject *map;
int err;
根据文档预期的评估结果,键在值之前评估。 (不适用于异步版本,这是需要的另一个开关。)
我会对该问题发表评论,并在有人回复我时进行更新。
Created 问题 29652 -- 修复字典理解中键/值的求值顺序 http://bugs.python.org/issue29652在追踪器上。当问题取得进展时将更新问题。