wim*_*wim 56 python hash set python-2.x python-internals
为什么set函数调用会消除欺骗,但解析集合文字却没有?
>>> x = Decimal('0')
>>> y = complex(0,0)
>>> set([0, x, y])
{0}
>>> {0, x, y}
{Decimal('0'), 0j}
Run Code Online (Sandbox Code Playgroud)
(Python 2.7.12.可能与此类似问题的根本原因相同)
Mar*_*ers 55
设置相等的测试,直到有新的Python版本,它们执行此操作的顺序可能会根据您将值移交给正在构建的集合的形式而有所不同,如下所示.
既然0 == x是真的并且 0 == y是真的,但是x == y是假的,这里的行为实际上是未定义的,因为x == y如果前两个测试也是真的,则集合假定必须为真.
如果反转传递给的列表set(),则获得与使用文字相同的输出,因为相等测试的顺序发生了变化:
>>> set([y, x, 0])
set([0j, Decimal('0')])
Run Code Online (Sandbox Code Playgroud)
和翻转文字一样:
>>> {y, x, 0}
set([0])
Run Code Online (Sandbox Code Playgroud)
发生的事情是set literal将值加载到堆栈上,然后堆栈值以相反的顺序添加到新的set对象.
只要首先0加载,然后在集合中测试其他两个对象.在首先加载其他两个对象之一的时刻,相等测试失败并且您添加了两个对象:0
>>> {y, 0, x}
set([Decimal('0'), 0j])
>>> {x, 0, y}
set([0j, Decimal('0')])
Run Code Online (Sandbox Code Playgroud)
这个set literals反向添加元素是所有支持语法的Python版本中存在的错误,一直到Python 2.7.12和3.5.2.它最近已修复,见问题26020(2.7.13,3.5.3和3.6的一部分,尚未发布).如果你看一下2.7.12,你可以看到,BUILD_SET在ceval.c读取来自自上而下的堆栈:
# oparg is the number of elements to take from the stack to add
for (; --oparg >= 0;) {
w = POP();
if (err == 0)
err = PySet_Add(x, w);
Py_DECREF(w);
}
Run Code Online (Sandbox Code Playgroud)
而字节码以相反的顺序向堆栈添加元素(首先推送0堆栈):
>>> from dis import dis
>>> dis(compile('{0, x, y}', '', 'eval'))
2 0 LOAD_CONST 1 (0)
3 LOAD_GLOBAL 0 (x)
6 LOAD_GLOBAL 1 (y)
9 BUILD_SET 3
12 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
修复是以相反的顺序从堆栈中读取元素; 所述的Python 2.7.13版使用PEEK()代替POP()(和STACKADJ()事后除去从堆栈中的元素):
for (i = oparg; i > 0; i--) {
w = PEEK(i);
if (err == 0)
err = PySet_Add(x, w);
Py_DECREF(w);
}
STACKADJ(-oparg);
Run Code Online (Sandbox Code Playgroud)
平等测试问题与另一个问题具有相同的根本原因; 这个Decimal()类complex在这里遇到了一些平等问题,这在Python 3.2中得到了修复(通过Decimal()支持比较complex以及之前不支持的一些其他数字类型).
这完全取决于构造集合的顺序,以及您在其他问题中发现的错误.似乎文字的构造顺序与从列表转换的顺序相反.
>>> {0, x, y}
set([0j, Decimal('0')])
>>> {y, x, 0}
set([0])
Run Code Online (Sandbox Code Playgroud)