# Why bool((i for i in range(0))) is True

Hello! Recently I’ve found that bool(“AN EMPTY GENERATOR”) is True.

``````code : print(bool(range(0)), bool((i for i in range(0))), bool(list((i for i in range(0)))))
output: False True False
``````

I think it’s a little bit weired, just like bool(“AN EMPTY LIST”) is False, what do you think ?In addition, I’ve tried calling next() on an “empty” generator, it raised StopIteration exception.

Generators are true even if they turn out to be empty because you can’t tell whether it’s empty or not until you try iterating over it, and objects are, by default, true.

1 Like

Thanks for your reply, so if I want to know whether a generator empty or not, I have to cast it to a list first.Or is there any better way to do this?

If the only thing you want to know is “is this empty”, you can probe it for just one element - if you get one, it’s not empty:

``````try: next(gen)
except StopIteration: print("It's empty")
``````

But this is a destructive operation, and you will lose the first element.

1 Like

I see, thank you.

Just opining, but maybe it would be useful for a generator supported a `push` operation which pushes a value onto the front of the generator. That value would be returned before actually touching the underlying generator.

Trivial half-assed implementation of the idea:

``````class gen2:
def __init__(self, gen1):
self.pushes = []
self.gen1 = gen1

def __iter__(self):
return self

def push(self, val):
self.pushes.insert(0, val)

def __next__(self):
if self.pushes:
return self.pushes.pop(0)
return next(self.gen1)

x = gen2(i for i in range(10))
x.push(-1)
print(next(x))
print(next(x))
x.push(42)
for i in x:
print(">>", i)
``````

When run, it outputs:

``````-1
0
>> 42
>> 1
>> 2
>> 3
>> 4
>> 5
>> 6
>> 7
>> 8
>> 9
``````
1 Like

Hi Skip, Inspired by your code.My idea is to create a copy of the original generator and then, when calling the `__bool__` function, use the copy to determine if it’s empty.Here is the half-assed code:

``````import itertools
class gen3:
def __init__(self, gen1):
self.gen1, self._gen2 = itertools.tee(gen1, 2)
self._empty = False
def __iter__(self):
return self

def __next__(self):
r = next(self.gen1, False)
if r is not False:
return r
self._empty = True
raise StopIteration

def __bool__(self):
if self._empty:
return False
try:
next(self._gen2)
return True
except StopIteration:
return False

x = gen3(i for i in range(10))
print(bool(x))
for i in x:
print(i)
print(bool(x))
x1 = gen3(i for i in range(0))
print(bool(x1))
``````

outputs:

``````True
0
1
2
3
4
5
6
7
8
9
False
False
``````

refs:

Optimization:

``````# removed self._empty
import itertools
class gen3:
def __init__(self, gen1):
self.gen1, self._gen2 = itertools.tee(gen1, 2)

def __iter__(self):
return self

def __next__(self):
r = next(self.gen1, False)
if r is not False:
return r
self._gen2 = self.gen1
raise StopIteration

def __bool__(self):
try:
next(self._gen2)
return True
except StopIteration:
return False

x = gen3(i for i in range(10))
print(bool(x))
for i in x:
print(i)
print(bool(x))
x1 = gen3(i for i in range(0))
print(bool(x1))
``````

output is the same.

1 Like

more_itertools.peekable has `__bool__` working as you desire (and has `prepend` and peeking).

3 Likes

Thanks, I’ve tried more_itertools.peekable , it works!

1 Like

Shows the lispy roots. Basically nil and empty list are false and False is false and 0 is false. Pretty much everything else is true. It is convenient mostly that it is so.

1 Like

For a succinct accounting of what is considered false during testing for truth value, we can find official documentation at Truth Value Testing. As stated, that would include, among other things, empty sequences and collections such as `''`, `()`, `[]`, `{}`, `set()`, and `range(0)`.

According to your answer, I’d like to post the source code I found in Cpython here.

``````int
PyObject_IsTrue(PyObject *v)
{
Py_ssize_t res;
if (v == Py_True)
return 1;
if (v == Py_False)
return 0;
if (v == Py_None)
return 0;
else if (Py_TYPE(v)->tp_as_number != NULL &&
Py_TYPE(v)->tp_as_number->nb_bool != NULL)
res = (*Py_TYPE(v)->tp_as_number->nb_bool)(v);
else if (Py_TYPE(v)->tp_as_mapping != NULL &&
Py_TYPE(v)->tp_as_mapping->mp_length != NULL)
res = (*Py_TYPE(v)->tp_as_mapping->mp_length)(v);
else if (Py_TYPE(v)->tp_as_sequence != NULL &&
Py_TYPE(v)->tp_as_sequence->sq_length != NULL)
res = (*Py_TYPE(v)->tp_as_sequence->sq_length)(v);
else
return 1;
/* if it is negative, it should be either -1 or -2 */
return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int);
}
``````