Литерал Set дает другой результат от вызова функции set


почему set вызов функции уничтожает обманы, но разбор литерала набора не делает?

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

(Python 2.7.12. Возможно, та же первопричина, что и для этой аналогичный вопрос)

2 56

2 ответа:

устанавливает тест на равенство, и пока не появятся новые версии Python, порядок, в котором они это делают, может отличаться в зависимости от формы, в которой вы передаете значения в создаваемый набор, как я покажу ниже.

С 0 == x истинно и0 == y - это верно, но x == y и ложные поведение здесь действительно неопределено, так как набор предполагает, что x == y должно быть истинно, если первые два теста тоже были истинными.

если вы реверс список передан set(), тогда вы получаете тот же результат, что и при использовании литерала, потому что порядок тестов равенства изменяется:

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

и то же самое для заднего хода дословный:

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

что происходит, так это то, что набор литерал загружает значения в стек, а затем значения стека добавляются в новый объект set в обратном порядке.

пока 0 загружается первый, другие два объекта затем тестируются против 0 уже в наборе. В тот момент, когда один из двух других объектов загружается первым, тест равенства не выполняется, и вы получаете два объекта добавлены:

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

что набор литералов добавить элементы в обратном направлении является ошибка присутствует во всех версиях 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);
}

в то время как байт-код добавляет элементы в стек в обратном порядке (нажимая 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 и несколько других числовых типов, которые он не поддерживал раньше).

все сводится к порядку, в котором построен набор, в сочетании с ошибкой, которую вы обнаружили с ваш вопрос. Похоже, что литерал строится в порядке, противоположном преобразованию из списка.

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