让我们说我在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
?
答案 0 :(得分:19)
字典评估顺序应该与写入相同,但有一个outstanding bug 值在键之前进行评估。 (该错误最终在Python 3.5中修复)。
Python从左到右评估表达式。
并从错误报告中获取:
运行以下代码显示
"2 1 4 3"
,但在参考手册中 http://docs.python.org/reference/expressions.html#expression-lists 评估顺序描述为{expr1: expr2, expr3: expr4}
def f(i): print i return i {f(1):f(2), f(3):f(4)}
和圭多说:
我坚持以前的观点:代码应该修复。它看起来不像是我的任务。
这个错误在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}
由于您的代码不需要首先评估密钥,因此您的代码可以保证正常工作;即使在每个相应值之后评估键,仍然按顺序评估键值对。
答案 1 :(得分:6)
根据Python docs regarding evaluation order,这应该有明确定义的行为:
在以下几行中,表达式将按其后缀的算术顺序进行评估:
… {expr1: expr2, expr3: expr4} …
因此,无论dict
中的项目以什么顺序结束迭代,文字字典表达式的值(和键!)将始终评估< / em>的顺序与我在Python源代码中出现的顺序相同。
答案 2 :(得分:2)
在反汇编的字节码中可以非常清楚地看到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
按此顺序预期值和键;更改顺序要么需要在每对之后添加ROT_TWO
操作码,要么
STORE_MAP_EX
操作码可以预期这些对被反转;第一个是性能下降,而第二个意味着要处理每个处理字节码的代码中的另一个操作码。