Python dict 文字会按照它的写入顺序进行评估吗?

Will a Python dict literal be evaluated in the order it is written?

假设我在 Python 遇到了这样的情况:

_avg = {'total':0.0, 'count':0}    # HACK: side-effects stored here

def procedure(n):
  _avg['count'] += 1
  _avg['total'] += n
  return n

def get_average():
  return _avg['total'] / _avg['count']

my_dict = {
  'key0': procedure(0),
  'key2': procedure(2),
  'key1': get_average()
}
assert(my_dict['key1'] == 1.0)

我知道 my_dict.keys() 的顺序是未定义的,但我想知道的是 初始化 通过这样的文字是否保证发生在特定顺序。 my_dict['key1'] 的值是否始终如断言的那样 1.0

根据 Python docs regarding evaluation order,这应该具有明确定义的行为:

In the following lines, expressions will be evaluated in the arithmetic order of their suffixes:

…
{expr1: expr2, expr3: expr4}
…

因此,无论 dict 中的项目最终被 迭代 的顺序如何,文字字典表达式的值(和键!)将始终是评估 的顺序与它们在我的 Python 源代码中出现的顺序相同。

字典求值顺序应该和写的一样,但是有一个outstanding bug,其中。 (该错误最终在 Python 3.5 中修复)。

引自reference documentation

Python evaluates expressions from left to right.

以及错误报告:

Running the following code shows "2 1 4 3", but in reference manual http://docs.python.org/reference/expressions.html#expression-lists the evaluation order described as {expr1: expr2, expr3: expr4}

def f(i):
    print i
    return i

{f(1):f(2), f(3):f(4)}

Guido 表示:

I am sticking with my opinion from before: the code should be fixed. It doesn't look like assignment to me.

此错误已在 Python 3.5 中修复,因此在 Python 3.4 和更早版本中,值仍然在键之前计算:

>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=4, micro=2, releaselevel='final', serial=0)
>>> def f(i):
...     print(i)
...     return i
... 
>>> {f(1):f(2), f(3):f(4)}
2
1
4
3
{1: 2, 3: 4}

由于您的代码不需要首先评估密钥,因此您的代码可以保证正常工作;即使在每个对应值之后评估键,键值对仍然按顺序评估。

Python 3.4.2 上的当前行为可以在反汇编的字节码中非常清楚地看到:值在键之前计算,而不是从左到右。

>>> dis.dis(lambda: {f('1'): f('2'), f('3'): f('4')})
  1           0 BUILD_MAP                2
              3 LOAD_GLOBAL              0 (f)
              6 LOAD_CONST               1 ('2')
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 LOAD_GLOBAL              0 (f)
             15 LOAD_CONST               2 ('1')
             18 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             21 STORE_MAP
             22 LOAD_GLOBAL              0 (f)
             25 LOAD_CONST               3 ('4')
             28 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             31 LOAD_GLOBAL              0 (f)
             34 LOAD_CONST               4 ('3')
             37 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             40 STORE_MAP
             41 RETURN_VALUE

然而,这也说明了为什么这也不是那么容易修复的原因:值和键在每对之后由 STORE_MAP in this order; changing the order would either require adding a ROT_TWO 操作码期望,或者 STORE_MAP_EX 期望对被反转的操作码;第一个将是性能下降,而第二个将意味着在处理字节码的每一段代码中都需要处理另一个操作码。