设置相等性测试,直到有新的 Python 版本,它们执行此操作的顺序可能会根据您将值传递给正在构造的集合的形式而有所不同,如下所示。
由于0 == x 为真并且0 == y 为真,但x == y 为假,这里的行为真的是未定义 ,因为该集合假设如果前两个测试也为真,则x == y 必须为真。
如果您反转传递给set() 的列表,那么您将获得与使用文字相同的输出,因为相等测试的顺序发生了变化:
>>> set([y, x, 0])
set([0j, Decimal('0')])
反转文字也是如此:
>>> {y, x, 0}
set([0])
发生的事情是集合literal将值加载到堆栈上,然后堆栈值以相反的顺序添加到新的集合对象中。
只要0 被加载首先,其他两个对象就会针对集合中的0 进行测试。其他两个对象之一首先加载的那一刻,相等性测试失败,您会添加两个对象:
>>> {y, 0, x}
set([Decimal('0'), 0j])
>>> {x, 0, y}
set([0j, Decimal('0')])
集合文字反向添加元素是所有支持该语法的 Python 版本中都存在的错误,一直到 Python 2.7.12 和 3.5.2。它最近已修复,请参阅issue 26020(2.7.13、3.5.3 和 3.6 的一部分,尚未发布)。如果你看2.7.12,你可以看到BUILD_SET in 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);
}
而字节码以相反的顺序将元素添加到堆栈中(首先将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
修复方法是从堆栈中以相反的顺序读取元素; Python 2.7.13 version 使用 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);
平等测试问题的根本原因与其他问题相同; Decimal() 类在此处与 complex 存在一些相等问题,这在 Python 3.2 中已修复(通过制作 Decimal() support comparisons to complex and a few other numeric types it didn't support before)。