In Python, we know that we can add two integers together:
>> 2 + 2 4
And of course, we can add two strings together:
>> 'a' + 'b' 'ab'
We can even add two lists together:
>> [1,2,3] + [4,5,6] [1, 2, 3, 4, 5, 6]
But what happens when you add two booleans together?
>> True + True 2
Huh? What’s going on here?
This would seem to be the result of several different things going on.
First of all, booleans in Python are actually equal to 1 and 0. We know that in a boolean context (i.e., in an “if” statement or “while” loop), 1 is considered to be True, and 0 is considered to be False. But then again, 10, 100, and ‘abc’ are also considered to be True, while the empty string is considered to be False. All values are turned into booleans (using __nonzero__ in Python 2, and __bool__ in Python 3) when they’re in a place that requires a boolean value.
But whereas 10 and the empty string are turned into True and False (respectively) in a boolean context, 1 and 0 are special. True and False are really equal to 1 and 0, as we can see here:
>> True == 1 True >>> True == 10 False >>> False == 0 True >>> False == '' False
So, it would seem that while it’s preferable to use True and False, rather than 1 and 0, the difference is a semantic one, for programmers. Behind the scenes, they’re really numbers.
But how can that be? Why in the world would Python consider two objects of completely different types to have equal values?
The answer is that in Python, the “bool” type is actually a subclass of “int”, as we can see here:
>> bool.__bases__ (<class 'int'>,)
So while it’s still a bit weird for two values with different types to be considered equal, it’s not totally weird, given that booleans are basically a specialized form of integers. And indeed, if we look at the documentation for booleans, we find that it implements very few methods of its own; most are inherited from int. One of the many inherited operators is __eq__, the method that determines whether two objects are equal. Which means that when we’re comparing 1 and True, we’re really just comparing 1 (of type int) and 1 (of type bool, a subclass of int, which means that it behaves like an int).
This, then, explains what’s going on: When we say “True + True”, the int.__add__ operator is called. It gets two instances of bool, each with a value of 1, and which know how to behave as if they were regular ints. So we get 2 back.
Just for kicks, what happens if we say
True += 1
in Python 3, we get an error, saying that we can’t assign to a keyword. That’s because in Python 3, True and False are true keywords, rather than names defined in the “builtins” namespace. But in Python 2, they’re just built-in names. Which means that this is what happens in Python 2:
>>> True += 1 >>> True 2 >>> type(True) <type 'int'>
Look at what we did: Not only did we add True + 1, but we then assigned this integer back to True! Which means that True is no longer an instance of “bool” in the “builtins” namespace, but rather an integer.
How did this happen? Because of Python’s scoping rules, known as “LEGB” for local-enclosing-globals-builtins. When we assigned to True with +=, we didn’t change the value of True in builtins. Rather, we created a new global variable. The builtin value is still there, but because the global namespace gets priority, its value (2) masks the actual value (True, or 1) in the builtins namespace.
Indeed, look at this:
>>> True += 1 >>> True == __builtins__.True False
How can we get out of this situation? By embracing nihilism, deleting truth (i.e., the True name we’ve defined in the global namespace). Once we’ve done that, True in the builtins namespace will still exist:
>>> del(True) >>> True True
I’d like to think that none of us will ever be doing mathematical operations with booleans, or trying to assign to them. But understanding how these things work does provide a clearer picture of Python scoping rules, which govern how everything else in the language functions.
What other weird boolean/integer behavior have you noticed? I’m always curious to find, and then understand, these somewhat dark (and useless) aspects of Python!
Unfamiliar with Python scoping rules, or just want to brush up on them? Take my free, five-part “Python variable scoping” e-mail course: http://lerner.co.il/e-mail-courses/variable-scoping-in-python/