Why does the set
function call wipe out the dupes, but parsing a set literal does not?
>>> x = Decimal('0')
>>> y = complex(0,0)
>>> set([0, x, y])
{0}
>>> {0, x, y}
{Decimal('0'), 0j}
(Python 2.7.12. Possibly same root cause as for this similar question)
It's all down to the order in which the set is constructed, combined with the bug you discovered with your other question. It appears that the literal is constructed in the opposite order to conversion from a list.
Sets test for equality, and until there are new Python releases, the order in which they do this can differ based on the form you hand the values to the set being constructed, as I'll show below.
Since
0 == x
is true and0 == y
is true, butx == y
is false, the behaviour here is really undefined, as the set assumes thatx == y
must be true if the first two tests were true too.If you reverse the list passed to
set()
, then you get the same output as using a literal, because the order of equality tests changes:and the same for reversing the literal:
What's happening is that the set literal loads the values onto the stack and then the stack values are added to the new set object in reverse order.
As long as
0
is loaded first, the other two objects are then tested against0
already in the set. The moment one of the other two objects is loaded first, the equality test fails and you get two objects added:That set literals add elements in reverse is a bug present in all versions of Python that support the syntax, all the way until Python 2.7.12 and 3.5.2. It was recently fixed, see issue 26020 (part of 2.7.13, 3.5.3 and 3.6, none of which have been released yet). If you look at 2.7.12, you can see that
BUILD_SET
inceval.c
reads the stack from the top down:while the bytecode adds elements to the stack in reverse order (pushing
0
on the stack first):The fix is to read the elements from the stack in reverse order; the Python 2.7.13 version uses
PEEK()
instead ofPOP()
(and aSTACKADJ()
to remove the elements from the stack afterwards):The equality testing issue has the same root cause as the other question; the
Decimal()
class is having some equality issues withcomplex
here, which was fixed in Python 3.2 (by makingDecimal()
support comparisons tocomplex
and a few other numeric types it didn't support before).