42

I just started investigating the set data type in Python. For some reason, whenever I add the Boolean value of True to a set it doesn't appear. However, if I add False to a set it will become an element of the set. I was shocked when I googled this question that nothing came up.

example1 = {1, 2, 7, False}
example2 = {7, 2, 4, 1, True}

print(example1)
print(example2)

The output is:

{False, 1, 2, 7}
{1, 2, 4, 7}
DJ Poland
  • 955
  • 1
  • 9
  • 23
  • 1
    If `true` is 1 and `false ` is 0, true is already in the set so it won't be added again. – daniu Jul 24 '18 at 19:04
  • 2
    `print({0, False})` – DeepSpace Jul 24 '18 at 19:06
  • My Python 3.5.1 says `{True, 2, 4, 7}` and the 1 is gone. Python is sometimes unpythonic – Thomas Weller Jul 24 '18 at 21:15
  • 1
    @ThomasWeller Considering that `__future__` is still growing, it looks like Python 4 is going to be like Python 3 was to Python 2. – wizzwizz4 Jul 24 '18 at 21:44
  • @wizzwizz4 I do sincerely hope that Python 4 is a smoother transition than 3, with a lot more attention paid to compatibility between the versions. (E.g., having a way to use newer built in types that are modified in an incompatible way, à la `str` and `dict` in Python 3.) – jpmc26 Jul 24 '18 at 22:05
  • @ThomasWeller It's a set. There's no order guarantees. If two objects have the same hash and compare equal, there's no way of knowing which one will end up in the set, and your code shouldn't rely on any particular choice. – jpmc26 Jul 24 '18 at 22:07
  • @jpmc26 The biggest `__future__` changes seem to be with annotations and similarly "trivial" stuff; nothing major like `print`-is-no-longer-a-keyword. – wizzwizz4 Jul 24 '18 at 22:08
  • @jpmc26 If True == 1, how can Python even decide to print True instead of 1? – Thomas Weller Jul 24 '18 at 22:20
  • @ThomasWeller i’ve found so far, whichever is first will be outputted. For example, if you put {1,True, 5,6}, you’ll get 1 first whereas {True,1,5,6} gives me True. – DJ Poland Jul 24 '18 at 22:22
  • @DJPoland: Exactly opposite on my machine 3.5.1 and 2.7.11: `{1, 2, 7, True}` gives `{True, 2, 7}`. The last one wins – Thomas Weller Jul 24 '18 at 22:26
  • 2
    @ThomasWeller `id(1) == id(True)` gives `False`. They're different objects, despite being equal and having the same hash. `True` is an instance of `bool`, and `1` is an instance of `int`, which is a parent class of `bool`. `==` is not an identity comparison. The answer here is don't stuff `bool` and `int` in the same set. Why would you mix them? – jpmc26 Jul 24 '18 at 22:34
  • This reminds me of the classic `{True: 'yes', 1: 'no', 1.0: 'maybe'}` which evaluates to `{True: 'maybe'}` because `bool` is a subclass of `int` with values `0` and `1`, which means that `True` & `False` hash to the same dictionary or set keys and are therefore "identical" to their integer values in terms of set membership. – davidA Jul 25 '18 at 04:36
  • Now add in `0j`, `1j`, `decimal.Decimal('0')`, `decimal.Decimal('1')`, `fractions.Fraction(0, 1)`, `fractions.Fraction(1, 1)`, `0.0` and `1.0` into the mix. Sets test for uses hashing and *equality tests* to determine if an object is in the set. `1 == True == 1.0 == 1j == decimal.Decimal('1') == fractions.Fraction(1, 1)`, and they all have the same hash value. – Martijn Pieters Jul 31 '18 at 18:52

3 Answers3

46

Because in Python 1 == True (and hash(1) == hash(True)) and you have 1 in your set already.

Imagine this example:

example1 = {0, False, None}
example2 = {1, True}

print(example1)
print(example2)

Will output:

{0, None}
{1}

First set has 0 and None because 0 == False but 0 != None. With second set 1 == True so True isn't added to the set.

user2357112
  • 260,549
  • 28
  • 431
  • 505
Andrej Kesely
  • 168,389
  • 15
  • 48
  • 91
  • 1
    I might add that if you change the order (i.e. put the booleans before the int in the set literal definition) it will print out {False, None} and {True}. – Lucubrator Dec 04 '18 at 15:13
17

The reason that you are losing boolean values from a set if they already contain 0 or 1 is because the following behavior...

>>> hash(1) == hash(True)
True
>>> hash(0) == hash(False)
True
>>> 1 == True
>>> True
>>> 0 == False
>>> True

...is guaranteed in Python 3.x.

Which means that you cannot have both in a set:

>>> set([True, 1])
{True}
>>> set([False, 0])
{False}

The hashes being equal is just as important as the objects being equal, because objects that are "equal" can produce different hashes and vice versa:

class Foo:
    def __init__(self, x): self.x = x
    def __hash__(self): return 1
    def __eq__(self, other): return self.x == other.x

class Bar:
    def __init__(self, x): self.x = x
    def __hash__(self): return 2
    def __eq__(self, other): return self.x == other.x

>>> x = Foo(3)    
>>> y = Bar(3)
>>> x == y
True
>>> hash(x) == hash(y)
False
>>> set([x, y])
{<__main__.Bar at 0x56ed278>, <__main__.Foo at 0x5707390>}

You can also have a set that contains items with the same hashes, if those items are not equal:

>>> hash('a')
-904409032991049157
>>> hash(-904409032991049157)
-904409032991049157
>>> hash('a') == hash(-904409032991049157)
True
>>> set(['a', -904409032991049157])
{-904409032991049157, 'a'}

This behavior is not guaranteed in Python 2.x, for the simple reason that True and False are not reserved keywords (this change was introduced in 3.x). You may reassign them (although better not to), so there is no reason that the same behavior must hold in Python 2.x:

>>> True = 5
>>> hash(True) == hash(1)
False
>>> set([1, True])
set([1, 5])

But don't let the fact that True was replaced with 5 discourage you! We can abuse the representation of a class to make it appear as though True really is in the set:

class Foo(object):
    def __repr__(self):
        return('True')

>>> True = Foo()
>>> set([1, True])
set([1, True])

Obviously the last couple code snippets are bad practice, and are only for demonstration. The main takeaway is that equal objects with the same hash cannot be contained in the same set, and in Python 3.x, 1 and True, and 0 and False, will always have the same hash, and will always be equal.

user3483203
  • 50,081
  • 9
  • 65
  • 94
  • "objects that are "equal" can produce different hashes and vice versa" - if they do that, they're broken. For example, your `Foo` and `Bar` classes are broken because their `__eq__` doesn't return `NotImplemented` for unrecognized types. – user2357112 Aug 21 '18 at 00:16
1

False and True are equal to 0 and 1, respectively. They are distinct entities, yet the two equal values cannot both be in a set. This is clearly undesired behavior, yet it is not clear it can be fixed and still allow multiplying by a boolean value to work as documented.

IPython 6.2.1 -- An enhanced Interactive Python.

1 is True
Out[1]: False

{1,True}
Out[2]: {1}

{0,False}
Out[3]: {0}

{False, 0}
Out[4]: {False}

{True, 1}
Out[5]: {True}

Notice that depending on the order of putting them into the set, 1 will not be in the set if True is already in it, and True will not be in the set if 1 is already in it.

Bit Chaser
  • 186
  • 6
  • 12