38

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
dukebody
  • 7,025
  • 3
  • 36
  • 61
  • 3
    [Here's a bunch of examples](http://www.petercollingridge.co.uk/python-tricks/boolean-indices). – Ken Y-N Jun 30 '16 at 08:21
  • 21
    Useful for code golf : print(('ko', 'ok')[a – polku Jun 30 '16 at 08:21
  • 8
    It's valid python code, whether it's "legit" use is then mostly a matter of opinion. – skyking Jun 30 '16 at 08:26
  • 2
    Interesting discussion here: http://stackoverflow.com/questions/2764017/is-false-0-and-true-1-in-python-an-implementation-detail-or-is-it-guarante – cdarke Jun 30 '16 at 08:30
  • @skyking When I say 'legit' I mean an useful use case where there is likely no better, more readable and maintainable and less magical option in the language. Although I guess this last thing is also a matter of opinion. :) – dukebody Jun 30 '16 at 09:01
  • 1
    @dukebody "better", "more readable", "maintainable" and "magical" are all matter of opinion... – skyking Jun 30 '16 at 10:42
  • 4
    Note that in numpy `ndarray`, it has a different meaning: `np.array([1, 2, 3])[array([True, False, True])]` → `array([1, 3])`. – gerrit Jun 30 '16 at 13:27
  • This is far too opinion-based. Depending on your definition of "legit", the entire [PPCG.SE site](http://codegolf.stackexchange.com) may qualify - we exploit the fact that `False == 0` and `True == 1` regularly to write short code. –  Jul 01 '16 at 10:33

3 Answers3

60

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.

Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • but how does it actually work Martin? i tried list l = [2, 0, 1] just to see what it yields adn False returns 2 and True 0.. – Ma0 Jun 30 '16 at 08:19
  • 7
    @Ev.Kounis: `True == 1`, `False == 0`, because booleans are a subclass of `int`. Since `eggs > 5` produces a boolean, but indexing takes the integer value, `eggs > 5` being `True` produces `['foo', 'bar'][1]`, otherwise `['foo', 'bar'][0]` is produced. – Martijn Pieters Jun 30 '16 at 08:20
  • Is the alternative form still clearer if the entity you are indexing is verbose? `x = available_options[choice].response_text[ int(n<9) ]` maybe? I'd certainly argue for always putting that `int()` there to make it more readable, even though it's a no-op by language definitions. – nigel222 Jun 30 '16 at 16:18
  • @nigel222: I'd prefer `opts = available_options[choice].response_text`, and `x = opts[0] if n < 9 else opts[1]`. – Martijn Pieters Jun 30 '16 at 16:20
  • Alternatively, I personally find a dict for mapping a lot easier to quickly scan over: `{True: opts[0], False: opts[1]}[n < 9]`. (Or if you want to get a bit crazier, `opts[ {True: 0, False: 1}[n < 9] ]`) – Izkata Jun 30 '16 at 17:03
  • @Izkata: but again that negates the short-circuit behaviour. Why all the need for obscurity? – Martijn Pieters Jun 30 '16 at 17:04
  • @MartijnPieters For clarity. Taking advantage of this particular short-circuit is a C-like mentality, and takes more mental overhead. – Izkata Jun 30 '16 at 17:05
  • 5
    @Izkata: I really don't find that clearer, and it is very much flying in the face of Python idiom. – Martijn Pieters Jun 30 '16 at 17:06
  • Sorry, the C-like mentality I'm referring to is `int(True) == 1`, which I absolutely avoid. If short-circuiting is actually necessary (say, there's a function call involved), then I'll use the ternary. But my preference for the dict form is related to Python's way of trying to make everything sentence-like (`!` vs `not`, for example, and changing the order of the ternary) - it adds mental overhead parsing the line into a logic construction, where the symbolic way actually looks like the logic construction. – Izkata Jun 30 '16 at 17:14
35

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 ints 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 ints 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.
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
4

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])
JS.
  • 14,781
  • 13
  • 63
  • 75