【问题标题】:Set literal gives different result from set function call集合文字给出了与集合函数调用不同的结果
【发布时间】:2017-03-06 16:22:09
【问题描述】:

为什么set 函数调用可以消除欺骗,但解析集合文字却没有?

>>> x = Decimal('0')
>>> y = complex(0,0)
>>> set([0, x, y])
{0}
>>> {0, x, y}
{Decimal('0'), 0j}

(Python 2.7.12。可能与this类似问题的根本原因相同)

【问题讨论】:

标签: python hash set python-2.x python-internals


【解决方案1】:

这完全取决于集合的构建顺序,以及您在your other question 中发现的错误。看起来文字的构造顺序与从列表转换的顺序相反。

>>> {0, x, y}
set([0j, Decimal('0')])
>>> {y, x, 0}
set([0])

【讨论】:

  • 啊,是的,这很有见地。显示如何从右到左解析文字的 cpython 源参考怎么样?
  • @wim:我很难找到代码,只是因为最近修复了这个错误。
  • @wim 听起来很有趣,但我从来没有深入研究过 Python 源代码。
【解决方案2】:

设置相等性测试,直到有新的 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)。

【讨论】:

  • 你认为这三种类型的相等性不传递是一个错误吗?
  • @StevenRumbalski:我想是的,是的。当0j == 0 为真且Decimal('0') == 0 也为真时,为什么0j 不等于Decimal('0')。 Python 3.2 通过更新 Decimal 比较解决了这个问题。
  • bytearray('ab') == 'ab' == u'ab' 也破坏了传递性,但由于 bytearray 不可散列,因此在那里没有任何意义。
  • 问题 26020 很有趣。似乎更严重的错误是 complex 平等行为不端,所以我很惊讶在 python 2 中还没有得到修复。
  • @wim: complex 的使用远低于 set()。此外,这是Decimal 中的错误,而不是complex
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-30
  • 1970-01-01
  • 2019-10-13
  • 1970-01-01
  • 1970-01-01
  • 2019-10-31
  • 1970-01-01
相关资源
最近更新 更多