10 months ago

In reviews I often call out implicit boolean conditions:

while True:
    data = fd.read(4096)
    if not data:
        break
    do_something(data)

I usually wave my arms a bit, repeat "explicit is better than implicit", and maybe recall that one time this saved my bacon.

Now I have numbers to back me up.

$ python3 -m timeit -s 'a = b""' 'bool(a)'
10000000 loops, best of 3: 0.0956 usec per loop
$ python3 -m timeit -s 'a = b""' 'len(a) == 0'
10000000 loops, best of 3: 0.05 usec per loop
$ python3 -m timeit -s 'a = b"123"' 'bool(a)'
10000000 loops, best of 3: 0.0905 usec per loop
$ python3 -m timeit -s 'a = b"123"' 'len(a) == 0'
10000000 loops, best of 3: 0.0502 usec per loop

Evaluating an implicit boolean condition is at least 80% slower than being explicit.

Well, for byte strings at least.

Let's try numbers:

$ python3 -m timeit -s 'a = 0' 'bool(a)'
10000000 loops, best of 3: 0.0839 usec per loop
$ python3 -m timeit -s 'a = 0' 'a == 0'
10000000 loops, best of 3: 0.0235 usec per loop
$ python3 -m timeit -s 'a = 0.0' 'bool(a)'
10000000 loops, best of 3: 0.0886 usec per loop
$ python3 -m timeit -s 'a = 0.0' 'a == 0.0'
10000000 loops, best of 3: 0.0323 usec per loop

Evaluating a number in an implicit boolean context is about 300% slower. That's actually a little weird how bad that is.

Next I put this in foo.py:

class LenZero:
    def __len__(self):
        return 0

class LenOne:
    def __len__(self):
        return 1

l0 = LenZero()
l1 = LenOne()

This is interesting:

$ python3 -m timeit -s 'from foo import l0' 'bool(l0)'
1000000 loops, best of 3: 0.231 usec per loop
$ python3 -m timeit -s 'from foo import l0' 'len(l0) == 0'
1000000 loops, best of 3: 0.218 usec per loop
$ python3 -m timeit -s 'from foo import l1' 'bool(l1)'
1000000 loops, best of 3: 0.237 usec per loop
$ python3 -m timeit -s 'from foo import l1' 'len(l1) == 0'
10000000 loops, best of 3: 0.197 usec per loop

Being implicit here only costs 5-20%.

I replaced foo.py with:

class BoolFalse:
    def __bool__(self):
        return False

class BoolTrue:
    def __bool__(self):
        return True

bf = BoolFalse()
bt = BoolTrue()

The purpose of __bool__ is for implicit boolean evaluation, so we can't compare implicit versus explicit here, but we can see that it's similar in speed to using __len__:

$ python3 -m timeit -s 'from foo import bf' 'bool(bf)'
1000000 loops, best of 3: 0.209 usec per loop
$ python3 -m timeit -s 'from foo import bt' 'bool(bt)'
1000000 loops, best of 3: 0.207 usec per loop

These timings are small enough that they're not going to hurt unless they're in a tight loop, say. But being explicit is a good habit to cultivate anyway <waves arms> and now you've got the excuse that it's faster.

← Python: sets versus lists Bazaar repositories for fun/profit/shenanigans →