Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bool value of a list in Python

what is the best way to turn a list into bool value? I am looking for something like:

return eval_bool(my_list)

I have a custom container in which I implement the __nonzero__ method which is supposed to work like this:

if self.my_list:
    return True
return False

But is it pythonic enough? :) Anyway, I am curious how Python interprets the value of the list in the if statement because this code works differently:

return my_list == True

J.

like image 358
galapah Avatar asked Mar 24 '12 08:03

galapah


2 Answers

Just use:

bool(my_list)

Which evaluates it as Python "truthiness" and returns a real Boolean.

like image 106
Keith Avatar answered Oct 02 '22 08:10

Keith


99.9% of the time, performance doesn't matter, so just use bool(my_list) as Keith suggests.

In the cases where performance does matter though, the nature of bool means it's actually quite slow, at least on the CPython reference interpreter. It has to go through generalized function call paths, to generalized constructor paths, to generalized argument parsing for 0-1 arguments (and in all but the most recent versions of Python, checking for keyword arguments), all to eventually just increment as reference count on a singleton and return it.

You can see how much this costs with ipython microbenchmarks (on my Windows x64 3.6.3 build):

In [1]: %%timeit -r5 l = []
   ...: bool(l)
   ...:
118 ns ± 0.808 ns per loop (mean ± std. dev. of 5 runs, 10000000 loops each)
In [11]: %%timeit -r5 l = [1]
    ...: bool(l)
    ...:
117 ns ± 0.306 ns per loop (mean ± std. dev. of 5 runs, 10000000 loops each)

It may not be obvious, but even on my relatively weak laptop, 117-118 nanoseconds just to determine truthiness is a bit much. Luckily, there are a couple other options. One is to abuse syntax to go through a dedicated path for truthiness evaluation (from here on out, I'll just test the empty list, the timings are basically identical either way):

In [3]: %%timeit -r5 l = []
   ...: not not l
   ...:
25 ns ± 0.289 ns per loop (mean ± std. dev. of 5 runs, 10000000 loops each)

That's a vast improvement; it takes roughly one fifth the time. On Python 3, using True if l else False also works with equal speed, but it's much slower than not not on Python 2, where True and False aren't protected literals, just built-in names that must be loaded dynamically each time.

Still, it's not perfect; sometimes you need a callable, e.g. to convert a lot of values to bool via a callback function (e.g. with map). Luckily, the operator module has you covered with operator.truth; while it's still a callable with all the overhead that entails, it's not a constructor, it takes exactly one argument (not 0-1), and it doesn't allow keyword arguments, all of which cost a surprising amount on the CPython reference interpreter. So when you can't use implicit truthiness testing or syntax based conversion with not not, and you still need the speed, operator.truth has you covered:

In [4]: from operator import truth

In [5]: %%timeit -r5 l = []
   ...: truth(l)
   ...:
52.1 ns ± 1.1 ns per loop (mean ± std. dev. of 5 runs, 10000000 loops each)

Twice as long as not not, but if you're using it with built-ins that call it repeatedly (like map) being able to push all the work to the C layer, avoiding byte code execution entirely, can still make it a win, and it's still well under half as costly as bool() itself.

Reiterating my earlier point though: 99.9% of the time, performance doesn't matter, so just use bool(my_list) as Keith suggests. I only mention this because I once had a scenario where that boolean conversion really was the hottest point in my code (verified through profiling), and using implicit truthiness testing (not even converting, just returning the list with the caller doing if myfunc():) shaved 30% off the runtime, and returning not not of the list still got nearly a 20% savings.

like image 31
ShadowRanger Avatar answered Oct 02 '22 08:10

ShadowRanger