字典理解中的操作顺序 [英] Order of operations in a dictionary comprehension

查看:77
本文介绍了字典理解中的操作顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了以下有趣的结构:

I came across the following interesting construct:

假设您有一个列表列表,如下所示:

assuming you have a list of lists as follows:

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

,并且您想使用0 -index元素作为键来根据它们创建字典.一个方便的方法是:

and you want to create a dict out of them with the 0-index elements being the keys. A handy way to do it would be this:

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

看来,pop在列表x的赋值之前,这就是为什么'captain'不在值中出现(已经弹出)的原因

As it seems, the pop precedes the assignment of list x as the value and that is why 'captain' does not appear in the values (it is already popped)

现在让我们更进一步,尝试获得类似以下的结构:

Now let's take this a step further and try to get a structure like:

# {'captain1': {'column1': 'foo1', 'column2': 'bar1', 'column3': 'foobar1'}, ...}

为此任务,我写了以下内容:

For this task I wrote the following:

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之后).

so the pop in this case happens after the inner dictionary is constructed (or at least after the zip).

那怎么可能?如何运作?

How can that be? How does this work?

问题不是关于如何执行,而是为什么会看到这种行为.

The question is not about how to do it but rather why this behavior is seen.

我正在使用Python版本 3.5.1.

I am using Python version 3.5.1.

推荐答案

注意:从python 3.8和

Note: As of Python 3.8 and PEP 572, this was changed and the keys are evaluated first.

tl; dr;直到Python 3.7 :即使Python 首先求值(表达式的右侧)根据语法以及基于字典理解的PEP .

tl;dr Until Python 3.7: Even though Python does evaluate values first (the right-side of the expression) this does appear to be a bug in (C)Python according to the reference manual and the grammar and the PEP on dict comprehensions.

尽管以前是已修复为字典显示,其中再次在键之前对值进行了评估,未对补丁进行修改,以包含dict-comprehension. 此要求也被其中一个核心开发人员提到邮件列表主题讨论了同一主题.

Though this was previously fixed for dictionary displays where values were again evaluated before the keys, the patch wasn't amended to include dict-comprehensions. This requirement was also mentioned by one of the core-devs in a mailing list thread discussing this same subject.

根据参考手册,Python从左至右评估表达式,从右至左评估赋值; dict-comprehension实际上是一个包含表达式的表达式,不是赋值 * :

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的规则人们期望expr1: expr2的评估方式与显示方式相似.因此,两个表达式都应遵循定义的顺序,expr1应该在expr2之前进行评估(并且,如果expr2包含其自身的表达式,那么它们也应该从左至右进行评估.)

where, according to the corresponding rule of the grammar one would expect expr1: expr2 to be evaluated similarly to what it does in displays. So, both expressions should follow the defined order, expr1 should be evaluated before expr2 (and, if expr2 contains expressions of its own, they too should be evaluated from left to right.)

dict-comps上的PEP还声明以下内容在语义上应等效:

The PEP on dict-comps additionally states that the following should be semantically equivalent:

dict理解的语义实际上可以在 通过将列表推导传递给内置库来存储Python 2.2 字典构造函数:

The semantics of dict comprehensions can actually be demonstrated in stock Python 2.2, by passing a list comprehension to the built-in dictionary constructor:

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

在语义上等同于:

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

在元组(i, chr(65+i))中按预期从左到右进行评估.

were the tuple (i, chr(65+i)) is evaluated left to right as expected.

根据表达式规则更改其行为当然会导致dict的创建不一致.字典理解和带赋值的for循环导致不同的评估顺序,但这很好,因为它仅遵循规则.

Changing this to behave according to the rules for expressions would create an inconsistency in the creation of dicts, of course. Dictionary comprehensions and a for loop with assignments result in a different evaluation order but, that's fine since it is just following the rules.

尽管这不是主要问题,但是应该解决(评估规则或文档)以消除这种情况.

Though this isn't a major issue it should be fixed (either the rule of evaluation, or the docs) to disambiguate the situation.

* 内部,这确实导致了对字典对象的赋值,但这不会破坏表达式应该具有的行为.用户对表达式的行为有期望,如参考手册中所述.

*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.

正如其他答复者所指出的那样,由于您在其中一个表达式中执行了变异操作,因此您会丢掉有关首先评估的内容的任何信息.像Duncan一样,使用print调用可以阐明已完成的工作.

As the other answerers pointed out, since you perform a mutating action in one of the expressions, you toss out any information on what gets evaluated first; using print calls, as Duncan did, sheds light on what is done.

有助于显示差异的功能:

A function to help in showing the discrepancy:

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参考手册》.)

and yes, this applies specifically for CPython. I am not aware of how other implementations evaluate this specific case (though they should all conform to the Python Reference Manual.)

在源代码中进行浏览始终是一件好事(并且您还发现了描述行为的隐藏注释),因此让我们窥视文件

Digging through the source is always nice (and you also find hidden comments describing the behavior too), so let's peek in compiler_sync_comprehension_generator of the file compile.c:

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;

这似乎是一个足够充分的理由,并且,如果如此判断,则应将其归类为文档错误.

this might seem like a good enough reason and, if it is judged as such, should be classified as a documentation bug, instead.

在我做的快速测试中,切换了这些语句(首先访问了VISIT(c, expr, elt);),同时还切换了相应的

On a quick test I did, switching these statements around (VISIT(c, expr, elt); getting visited first) while also switching the corresponding order in MAP_ADD (which is used for dict-comps):

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

基于文档的评估结果,键值先于值进行评估. (不是它们的异步版本,这是另一个需要的开关.)

results in the evaluation one would expect based on the docs, with the key evaluated before the value. (Not for their asynchronous versions, that's another switch required.)

我将对此问题发表评论,并在有人回复我时进行更新.

已创建问题29652-修复了跟踪器上dict理解中键/值的评估顺序.取得进展后将更新该问题.

Created Issue 29652 -- Fix evaluation order of keys/values in dict comprehensions on the tracker. Will update the question when progress is made on it.

这篇关于字典理解中的操作顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆