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

Jea*_*bre 11 python set python-internals

有人问这里时,把原因1Trueset1被保留.

这当然是因为1==True.但在哪些情况下1保留并保留哪些情况True

让我们来看看:

传递一个list来构建set而不是使用set符号:

>>> set([True,1])
{True}
>>> set([1,True])
{1}
Run Code Online (Sandbox Code Playgroud)

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

现在使用set符号:

>>> {True,1}
{1}
>>> {1,True}
{True} 
Run Code Online (Sandbox Code Playgroud)

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

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

Ale*_*ley 5

语言规范似乎不保证插入集合文字中元素的顺序.但是,Python 3.6已更改,因此它具有预期的从左到右的评估顺序.有关此更改的完整详细信息,请参阅此问题,以及在插入顺序中引入更改的提交.


为了描述的变化在一个位更详细地,构建组字面{True, 1}触发BUILD_SET操作码(与oparg等于2)后第一推动指针True1到虚拟机的内部堆栈.

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

while (--oparg >= 0) {
    PyObject *item = POP();
    if (err == 0)
        err = PySet_Add(set, item);
        Py_DECREF(item);
Run Code Online (Sandbox Code Playgroud)

自从1最后添加到堆栈以来,它首先被弹出,并且是插入到集合中的第一个对象.

在较新版本的Python(例如3.6)中,BUILD_SET操作码使用PEEK而不是POP:

for (i = oparg; i > 0; i--) {
    PyObject *item = PEEK(i);
    if (err == 0)
        err = PySet_Add(set, item);
        Py_DECREF(item);
Run Code Online (Sandbox Code Playgroud)

PEEK(i)从堆栈中取出第i 项目,因此{True, 1},首先将对象True添加到集合中.