Литерал 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 ответа:
устанавливает тест на равенство, и пока не появятся новые версии 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])