集合中的插入顺序(解析{}时)

时间:2017-09-01 16:17:40

标签: python set python-internals

有人问here为什么将1True放在仅set 1中的原因。

这当然是因为1==True。但是在哪种情况下会保留1并保留True的情况?

让我们看看:

传递list以构建set而不是使用set表示法:

>>> set([True,1])
{True}
>>> set([1,True])
{1}

似乎是合乎逻辑的:set在内部列表上进行迭代,并且不添加第二个元素,因为它等于第一个元素(注意set([True,1]) 不能屈服1,因为set无法知道列表中的内容。它甚至可能不是list而是可迭代的

现在使用set表示法:

>>> {True,1}
{1}
>>> {1,True}
{True} 

在这种情况下,似乎是以相反的顺序处理项目列表(在Python 2.7和Python 3.4上测试)。

但这有保证吗?或者只是一个实现细节?

3 个答案:

答案 0 :(得分:5)

语言规范似乎无法保证插入集合文字中元素的顺序。但是,Python 3.6已更改,因此它具有预期的从左到右的评估顺序。有关此更改的完整详细信息,请参阅issue以及引入广告订单更改的commit

为了更详细地描述更改,在首次推送指向{True, 1}和{{的指针后,构建集合文字BUILD_SET会触发True操作码(oparg等于2) 1}}到虚拟机的内部堆栈上。

在Python 3.4中,BUILD_SET使用以下循环将元素插入集合中(请注意,在我们的例子中oparg为2):

1

由于while (--oparg >= 0) { PyObject *item = POP(); if (err == 0) err = PySet_Add(set, item); Py_DECREF(item); 最后被添加到堆栈中,它首先被弹出,并且是插入到集合中的第一个对象。

在较新版本的Python(例如3.6)中,BUILD_SET操作码使用1代替PEEK

POP

PEEK(i)从堆栈中取出i th 项,因此对于for (i = oparg; i > 0; i--) { PyObject *item = PEEK(i); if (err == 0) err = PySet_Add(set, item); Py_DECREF(item); ,对象{True, 1}将首先添加到集合中。

答案 1 :(得分:1)

设置显示中的从左到右顺序由the documentation保证:

  

从左到右评估其元素并将其添加到设置对象

示例:

>>> def f(i, seq="left to right".split()): print(seq[i])
>>> {f(0), f(1), f(2)}
left
to
right
{None}

因此,{1, True}实际上是:

>>> S = set()
>>> S.add(1)
>>> S.add(True) # no effect according to docs
>>> S
{1}

该集可能只包含True1中的一个,因为它们与set观点重复:

>>> hash(1) == hash(True)
True
>>> 1 == True
True

在Python 3上保证1 == True。见Is `False == 0 and True == 1 in Python an implementation detail or is it guaranteed by the language?

  

布尔值:这些表示真值False和True [...]布尔值在几乎所有上下文中的行为分别类似于值0和1,例外的是当转换为字符串时,字符串“False”或者分别返回“True”。

如果{1, True}打印{True},那么它是bug ("set_display evaluation order doesn't match documented behaviour")。输出应与set([1, True])相同。它在最近的pypy,jython和cpython 2.7.13 +,3.5.3 +版本上按预期工作({1}

答案 2 :(得分:0)

从最新版本之一,dict保留顺序作为实现细节的副作用。在3.7中,可以保证这种行为。 也许它对集合文字也有一些影响。

Python 3.6.2:

>>> {True,1}
{True}
>>> {1,True}
{1}
>>> set([True,1])
{True}
>>> set([1,True])
{1}