【问题标题】:Dict/Set Parsing Order ConsistencyDict/Set 解析顺序一致性
【发布时间】:2016-04-09 23:53:42
【问题描述】:

采用可散列对象(例如dict 键或set 项)的容器。因此,字典只能有一个值为 11.0True 等的键(注意:稍微简化 - 允许哈希冲突,但这些值被认为是相等的)

我的问题是:解析顺序是否明确定义,生成的对象是否可跨实现预测?例如,OSX Python 2.7.11 和 3.5.1 像这样解释dict

>>> { True: 'a', 1: 'b', 1.0: 'c', (1+0j): 'd' }
{True: 'd'}

在这种情况下,似乎保留了第一个键和最后一个值。

类似,在set的情况下:

>>> { True, 1, 1.0, (1+0j) }
set([(1+0j)])

这里似乎保留了最后一个项。

但是(如 cmets 中所述):

>>> set([True, 1, 1.0])
set([True])

现在迭代中的第一个被保留。

文档指出项目的顺序(例如在dict.items 中)是未定义的,但是我的问题是指构造 dictset 对象的结果。

【问题讨论】:

  • 但请注意set([ True, 1, 1.0, (1+0j) ]) 给出set([True])
  • 你是在严格地谈论 dict 和 set 字面量吗?
  • @TomKarzes 啊,这很有趣。将在上面添加。
  • @PadraicCunningham 不严格,不。更重要的是:这种行为是否已定义且一致。
  • @Padraic 文档说集合文字是“从左到右评估并添加到集合对象”。但我们所看到的却恰恰相反:它们是使用{} 表示法从右到左添加的集合。请记住,添加的第一个值是保留的值。

标签: python dictionary set python-internals


【解决方案1】:
  • @jsf's answer 中所述,错误 现在已在最新版本的 python 中得到修复

dictionary-displays

如果给出了一个逗号分隔的键/数据对序列,它们从左到右被评估以定义字典的条目:每个键对象都用作字典中的键以存储相应的数据。这意味着您可以在键/数据列表中多次指定同一个键,并且该键的最终字典值将是最后一个给定的值。

与列表和集合推导相比,字典推导需要两个用冒号分隔的表达式,后跟通常的“for”和“if”子句。运行推导时,生成的键和值元素会按照它们产生的顺序插入到新字典中。

set displays

集合显示产生一个新的可变集合对象,其内容由表达式序列或推导式指定。当提供以逗号分隔的表达式列表时,它的元素从左到右求值并添加到集合对象中。当提供推导时,集合由推导产生的元素构成。

调用集合构造函数或使用推导式和普通文字是有区别的。

def f1():
    return {x for x in [True, 1]}

def f2():
    return set([True, 1])
def f3():
    return {True, 1}
print(f1())
print(f2())
print(f3())
import dis

print("f1")
dis.dis(f1)

print("f2")

dis.dis(f2)

print("f3")
dis.dis(f3)

输出:

{True}
{True}
{1}

它们的创建方式会影响结果:

    605           0 LOAD_CONST               1 (<code object <setcomp> at 0x7fd17dc9a270, file "/home/padraic/Dropbox/python/test.py", line 605>)
              3 LOAD_CONST               2 ('f1.<locals>.<setcomp>')
              6 MAKE_FUNCTION            0
              9 LOAD_CONST               3 (True)
             12 LOAD_CONST               4 (1)
             15 BUILD_LIST               2
             18 GET_ITER
             19 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             22 RETURN_VALUE
f2
608           0 LOAD_GLOBAL              0 (set)
              3 LOAD_CONST               1 (True)
              6 LOAD_CONST               2 (1)
              9 BUILD_LIST               2
             12 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             15 RETURN_VALUE
f3
611           0 LOAD_CONST               1 (True)
              3 LOAD_CONST               2 (1)
              6 BUILD_SET                2
              9 RETURN_VALUE

当你传递一个用逗号分隔的纯文字时,Python 只运行 BUILD_SET 字节码:

当提供一个逗号分隔的表达式列表时,它的元素从左到右被计算并添加到集合对象中。

理解的行:

当提供推导时,集合由推导产生的元素构成。

因此,感谢 Hamish 提交了 bug report,根据 Raymond Hettinger 在链接中的评论,它确实归结为 BUILD_SET 操作码罪魁祸首是 Python/ceval.c 中的 BUILD_SET 操作码,它不必要地向后循环 em>,其实现如下:

 TARGET(BUILD_SET) {
            PyObject *set = PySet_New(NULL);
            int err = 0;
            if (set == NULL)
                goto error;
            while (--oparg >= 0) {
                PyObject *item = POP();
                if (err == 0)
                    err = PySet_Add(set, item);
                Py_DECREF(item);
            }
            if (err != 0) {
                Py_DECREF(set);
                goto error;
            }
            PUSH(set);
            DISPATCH();
        }

【讨论】:

  • 是的,f3 的情况显然出乎意料。请注意此处的信息:bugs.python.org/issue26020 - 这有点学术性,在任何实际情况下都重要,但它肯定很有趣。谢谢你的详细解答!
  • @Hamish,很酷,我昨晚寻找了 BUILD_SET 实现,但我无法保持清醒的时间找到它,我会更新答案,谢谢你告诉我。
  • 您能否再强调一点,行为差异是一个 错误 现在已修复。 Python 保证 f1、f2、f3 的答案相同:def f4(): S = set(); S.add(True); S.add(1); return S 请参阅 Order of insertion in sets (when parsing {})
  • @jfs,当然,我添加了一条注释和指向您答案的链接。
猜你喜欢
  • 1970-01-01
  • 2018-04-04
  • 2018-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-26
  • 2014-01-25
相关资源
最近更新 更多