Is there any legitimate use of list[True], list[False] in Python?
Question:
Since True
and False
are instances of int
, the following is valid in Python:
>>> l = [0, 1, 2]
>>> l[False]
0
>>> l[True]
1
I understand why this happens. However, I find this behaviour a bit unexpected and can lead to hard-to-debug bugs. It has certainly bitten me a couple of times.
Can anyone think of a legit use of indexing lists with True
or False
?
Answers:
In the past, some people have used this behaviour to produce a poor-man’s conditional expression:
['foo', 'bar'][eggs > 5] # produces 'bar' when eggs is 6 or higher, 'foo' otherwise
However, with a proper conditional expression having been added to the language in Python 2.5, this is very much frowned upon, for the reasons you state: relying on booleans being a subclass of integers is too ‘magical’ and unreadable for a maintainer.
So, unless you are code-golfing (deliberately producing very compact and obscure code), use
'bar' if eggs > 5 else 'foo'
instead, which has the added advantage that the two expressions this selects between are lazily evaluated; if eggs > 5
is false, the expression before the if
is never executed.
If you are puzzled why bool
is a valid index argument: this is simply for consistency with the fact that bool
is a subclass of int
and in Python it is a numerical type.
If you are asking why bool
is a numerical type in the first place then you have to understand that bool
wasn’t present in old releases of Python and people used int
s instead.
I will add a bit of historic arguments. First of all the addition of bool
in python is shortly described in Guido van Rossum (aka BDFL) blogpost: The History of Python: The history of bool
, True
and False
. The type was added via PEP 285.
The PEP contains the actual rationales used for this decisions. I’ll quote some of the portions of the PEP below.
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?
=> No.
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
?
=> Yes.
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
values.
Because bool
inherits from int
, True+1
is valid and equals 2
, and
so on. This is important for backwards compatibility: because
comparisons and so on currently return integer values, there’s no
way of telling what uses existing applications make of these
values.
Because of backwards compatibility, the bool type lacks many
properties that some would like to see. For example, arithmetic
operations with one or two bool arguments is allowed, treating
False as 0 and True as 1. Also, a bool may be used as a sequence
index.
I don’t see this as a problem, and I don’t want evolve the
language in this direction either. I don’t believe that a
stricter interpretation of “Booleanness” makes the language any
clearer.
Summary:
- Backwards compatibility: there was plenty of code that already used
int
s 0
and 1
to represent False
and True
and some of it used those values in numerical computations.
- It wasn’t seen as a big deal to have a “non-textbook”
bool
type
- Plenty of people in the Python community wanted these features
- BDFL said so.
There are often better ways, but Boolean indices do have their uses. I’ve used them when I want to convert a boolean result to something more human readable:
test_result = run_test()
log.info("The test %s." % ('Failed', 'Passed')[test_result])
Since True
and False
are instances of int
, the following is valid in Python:
>>> l = [0, 1, 2]
>>> l[False]
0
>>> l[True]
1
I understand why this happens. However, I find this behaviour a bit unexpected and can lead to hard-to-debug bugs. It has certainly bitten me a couple of times.
Can anyone think of a legit use of indexing lists with True
or False
?
In the past, some people have used this behaviour to produce a poor-man’s conditional expression:
['foo', 'bar'][eggs > 5] # produces 'bar' when eggs is 6 or higher, 'foo' otherwise
However, with a proper conditional expression having been added to the language in Python 2.5, this is very much frowned upon, for the reasons you state: relying on booleans being a subclass of integers is too ‘magical’ and unreadable for a maintainer.
So, unless you are code-golfing (deliberately producing very compact and obscure code), use
'bar' if eggs > 5 else 'foo'
instead, which has the added advantage that the two expressions this selects between are lazily evaluated; if eggs > 5
is false, the expression before the if
is never executed.
If you are puzzled why bool
is a valid index argument: this is simply for consistency with the fact that bool
is a subclass of int
and in Python it is a numerical type.
If you are asking why bool
is a numerical type in the first place then you have to understand that bool
wasn’t present in old releases of Python and people used int
s instead.
I will add a bit of historic arguments. First of all the addition of bool
in python is shortly described in Guido van Rossum (aka BDFL) blogpost: The History of Python: The history of bool
, True
and False
. The type was added via PEP 285.
The PEP contains the actual rationales used for this decisions. I’ll quote some of the portions of the PEP below.
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?=> No.
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 fromint
?=> Yes.
In an ideal world,
bool
might be better implemented as a
separate integer type that knows how to perform mixed-mode
arithmetic. However, inheritingbool
fromint
eases the
implementation enormously(in part since all C code that calls
PyInt_Check()
will continue to work — this returns true for
subclasses ofint
). Also, I believe this is right in terms of
substitutability: code that requires anint
can be fed abool
and it will behave the same as0
or1
. Code that requires a
bool
may not work when it is given anint
; for example, 3 & 4
is 0, but both 3 and 4 are true when considered as truth
values.
Because
bool
inherits fromint
,True+1
is valid and equals2
, and
so on. This is important for backwards compatibility: because
comparisons and so on currently return integer values, there’s no
way of telling what uses existing applications make of these
values.
Because of backwards compatibility, the bool type lacks many
properties that some would like to see. For example, arithmetic
operations with one or two bool arguments is allowed, treating
False as 0 and True as 1. Also, a bool may be used as a sequence
index.I don’t see this as a problem, and I don’t want evolve the
language in this direction either. I don’t believe that a
stricter interpretation of “Booleanness” makes the language any
clearer.
Summary:
- Backwards compatibility: there was plenty of code that already used
int
s0
and1
to representFalse
andTrue
and some of it used those values in numerical computations. - It wasn’t seen as a big deal to have a “non-textbook”
bool
type - Plenty of people in the Python community wanted these features
- BDFL said so.
There are often better ways, but Boolean indices do have their uses. I’ve used them when I want to convert a boolean result to something more human readable:
test_result = run_test()
log.info("The test %s." % ('Failed', 'Passed')[test_result])