Ответ 1
Устанавливает проверку на равенство, и пока не появятся новые выпуски Python, порядок, в котором они выполняются, может различаться в зависимости от формы, которую вы передаете значения, к конструируемому набору, как я покажу ниже.
Так как 0 == x
истинно, а 0 == y
истинно, но x == y
является ложным, поведение здесь действительно undefined, так как множество предполагает, что x == y
должно быть истинным, если первые два теста были истинными тоже.
Если вы отмените список, переданный на set()
, вы получите тот же результат, что и с использованием литерала, потому что изменяется порядок тестов равенства:
>>> set([y, x, 0])
set([0j, Decimal('0')])
и то же самое для обращения к буквалу:
>>> {y, x, 0}
set([0])
Что происходит, так как набор литералов загружает значения в стек, а затем значения стека добавляются в новый заданный объект в обратном порядке.
Пока сначала загружается 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
в 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
и нескольких других числовых типов он не поддерживался до).