The Surprising Behavior of Boolean Indexing in Python: Using Booleans as an index?
Python is a language full of interesting quirks, and one of them is how it handles boolean values in indexing. Consider this snippet:
my_list = [0, 1]
print(my_list[True]) # Output: 1
Why does my_list[True]
return 1
? To understand this, we need to dive into how Python treats booleans in different contexts.
Booleans as Integers
In Python, the boolean constants True
and False
are actually instances of the bool
class, which is a subclass of int
. True
has a value of 1
, and False
has a value of 0
.
You can verify this with the isinstance()
function:
print(isinstance(True, int)) # Output: True
print(isinstance(False, int)) # Output: True
This means that in many contexts, True
behaves like 1
and False
behaves like 0
.
Booleans in Indexing
When you use a boolean as an index, Python uses its integer value. So my_list[True]
is equivalent to my_list[1]
, which returns the second element of the list (remember, indexing starts at 0).
Similarly, my_list[False]
would be equivalent to my_list[0]
, returning the first element.
Potential Issues
While this behavior can occasionally be useful, it can also lead to confusing and hard-to-debug code if used unintentionally.
Consider this example:
def get_item(my_list, index):
return my_list[index]
items = [0, 1]
result = get_item(items, items.index(1))
print(result) # Output: 1
Here, we expect get_item(items, items.index(1))
to return 1
, because items.index(1)
returns 1
, the index of 1
in items
.
But what if we accidentally pass a boolean to get_item
?
result = get_item(items, True)
print(result) # Output: 1
We might expect this to raise an error, but instead it quietly returns 1
, because True
is treated as the index 1
. This can lead to confusing bugs.
Preventing Unintended Boolean Indexing
To prevent issues caused by unintended boolean indexing, you can explicitly check the type of the index before using it:
def get_item(my_list, index):
if not isinstance(index, int):
raise TypeError("Index must be an integer.")
return my_list[index]
Now, if we try get_item(items, True)
, it will raise a TypeError
with a clear message.
Alternatively, you could convert the index to an integer explicitly:
def get_item(my_list, index):
return my_list[int(index)]
This will convert True
to 1
and False
to 0
intentionally, which might be desirable in some cases.
In general, it's best to be explicit about types and avoid relying on implicit conversions like this. By being clear and intentional in your code, you can avoid surprising behaviors and make your intentions more obvious to other developers (and to your future self!).