web-dev-qa-db-ja.com

setリテラルは、set関数呼び出しとは異なる結果を返します

なぜset関数呼び出しはdupeを一掃しますが、セットリテラルの解析はそうしませんか?

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

(Python 2.7.12。おそらく根本的な原因は this と同じです)

56
wim

セットは等しいかどうかをテストし、新しいPython=リリースが出るまで、これを行う順序は、作成するセットに値を渡すフォームに基づいて異なる場合があります。未満。

_0 == x_はtrueおよび_0 == y_はtrueですが、_x == y_はfalseなので、最初の2つのテストもtrueの場合、セットは_x == y_もtrueである必要があると想定しているため、ここでの動作は実際にはundefinedです。

set()に渡されたリストをreverseすると、等価テストの順序が変わるため、リテラルを使用した場合と同じ出力が得られます。

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

リテラルを逆にする場合も同じです。

_>>> {y, x, 0}
set([0])
_

何が起こっているかというと、セットliteralが値をスタックにロードし、スタック値が新しいセットオブジェクトに逆の順序で追加されるということです。

_0_がロードされている限りfirst、他の2つのオブジェクトは、すでにセットにある_0_に対してテストされます。他の2つのオブジェクトの1つが最初にロードされた瞬間に、等価テストが失敗し、2つのオブジェクトが追加されます。

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

セットリテラルが要素を逆に追加することは、Pythonのすべてのバージョンに存在するバグであり、Python 2.7.12および3.5までの構文をサポートします。 2.最近修正されました issue 2602 (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バージョン は、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()complexとの比較、および以前サポートされていなかった他のいくつかの数値型をサポート )。

55
Martijn Pieters

それはすべて、セットが構築された順序であり、- あなたの他の質問 で発見したバグと組み合わされています。リテラルはリストからの変換とは逆の順序で構築されているようです。

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