Python “bool” — wat?
what I learned about bool while implementing it for Batavia
The bool type for Python was proposed in PEP 285 by Guido van Rossum, and adopted in Python 2.3.
This PEP proposes the introduction of a new built-in type, bool,
with two constants, False and True. The bool type would be a
straightforward subtype (in C) of the int type, and the values
False and True would behave like 0 and 1 in most respects
Under the Review section of PEP 285, the questions of arithmetic using bools and bool inheriting from int were addressed:
4) Should we strive to eliminate non-Boolean operations on bools
in the future, through suitable warnings, so that for example
True+1 would eventually (in Python 3000) be illegal?
There's a small but vocal minority that would prefer to see
"textbook" bools that don't support arithmetic operations at
all, but most reviewers agree with me that bools should always
allow arithmetic operations.
6) Should bool inherit from int?
In an ideal world, bool might be better implemented as a
separate integer type that knows how to perform mixed-mode
arithmetic. However, inheriting bool from int eases the
implementation enormously (in part since all C code that calls
PyInt_Check() will continue to work -- this returns true for
subclasses of int). Also, I believe this is right in terms of
substitutability: code that requires an int can be fed a bool
and it will behave the same as 0 or 1. Code that requires a
bool may not work when it is given an int; for example, 3 & 4
is 0, but both 3 and 4 are true when considered as truth
That’s Python’s BDFL writing, so that’s what happened. Most of the time, nothing unusual happens as a result of these design decisions. This blog post is about the other times.
If you’d like to test your own knowledge of how bool behaves, do this 15-question quiz now, and then come back here and keep reading. (Note that the quiz specifies Python 3.5; some of the more interesting results are different in Python 3 compared with Python 2.)
Did you find the quiz easy? At the time of writing, with over 100 responses to the quiz, the average score is only 44%, and the highest score is 80%.
Let’s look at those questions and try to understand the “wat‽” in each one.
1. True + True
This result comes directly from the representation of True as 1, and bool’s inheritance from int: 1 + 1 = 2.
2. True – True
Same explanation as for question 1: 1 – 1 = 0.
Note that the font that medium.com uses makes it tricky to distinguish the digit zero (“o”) from the lowercase letter “o”; in this article any “0” not part of a word is a zero.
3. True % 1
The modulo operator % gives the remainder when divided by 1, which is always 0 for integers: 1 % 1 = 0.
4. True % 2
Our first real wat. 1 % 2 = 1, so why does Python return True instead?
Why does the type of the result change just because the value (not type) of one of the operands changes? I’ll pause that thought for a minute and pick it up after the next couple of questions.
5. False % 1
6. False % 2
At least 5 and 6 are consistent: 0 % 1 = 0 and 0 % 2 = 0, only Python is returning a bool for both, just like it did for question 4.
My guess is that there’s some kind of short-circuit evaluation going on, which returns the first operand of “a % b” without doing any type-casting if these conditions are met:
- a >= 0 and b > 0
- a < b
I’m not an expert on the CPython internals, so I haven’t been able to confirm this by digging around the source. (If you can confirm or deny this guess, please leave a comment.)
7. False % 1.0
Since one of the operands is a float, False is type-cast to a float before the operation, and the result is also a float.
8. True % True
This is really just question 3 again.
9. False % True
This is question 4 again.
The rest of the questions are more about complex numbers and division by zero exceptions, and many more people got those questions correct, so I’ll only cover them briefly.
10. False / 1j
In case you haven’t seen it before, “1j” is an imaginary literal, used to build up values of the complex type. In mathematics, j = sqrt(–1), a number that isn’t a member of the set of real numbers.
There’s no real trick to this question (pun intended); False is type-cast to complex, so the result will be of type complex.
11. True / –1j
All the possible answers to this question that were given are effectively equal, so the question was asking how Python represents complex numbers, and under what circumstances the real part of a complex number would be “–0” instead of “0” (positive zero).
- The real and imaginary parts of complex are always of type float, so Python doesn’t bother to add “.0” to numbers that appear to be integral.
- Python’s float implementation distinguishes 0 from –0, and the sign is negative if the operands have different signs. (Compare the case with floats: “0.0 / –1.0 = –0.0”.)
12. False / –1j
The explanation for question 11 applies here too.
13. True // False
Answer: ZeroDivisionError: integer division or modulo by zero
14. 0.0 / False
Answer: ZeroDivisionError: float division by zero
15. 0.0 // False
Answer: ZeroDivisionError: float divmod()
There’s no tricks here, just differently worded exception messages to choose from.
Just considering questions 1 to 9, which were the ones that dealt primarily with bool, the average score on the quiz was just 22%, and the highest score was 56%, both of which are much worse results than for the quiz overall.
From this I conclude that most people who answered the quiz don’t really understand the behaviour of Python’s bool type very well — but considering that the questions dealt mainly with obscure edge cases, it doesn’t really matter. 😛