【问题标题】:Will a Python dict literal be evaluated in the order it is written?Python dict 文字会按其编写顺序进行评估吗?
【发布时间】:2015-01-26 18:49:31
【问题描述】:

假设我在 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 dictionary


    【解决方案1】:

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

    引用reference documentation:

    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)}
    

    Guido 表示:

    我坚持我之前的观点:代码应该被修复。对我来说,这看起来不像是任务。

    此错误已在 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}
    

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

    【讨论】:

      【解决方案2】:

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

      在以下几行中,表达式将按照其后缀的算术顺序进行计算:

      …
      {expr1: expr2, expr3: expr4}
      …
      

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

      【讨论】:

      • 好电话。虽然如果这对您的代码真的很重要,您可能应该对此进行单元测试,特别是出于可移植性的原因。
      • bugs.python.org/issue11205除外,这表明该规则的Python实现存在错误。
      【解决方案3】:

      在反汇编的字节码中可以非常清楚地看到 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 操作码,期望这些对被反转;第一个将是性能下降,而第二个将意味着在处理字节码的每一段代码中要处理另一个操作码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-01
        • 2021-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多