Is there a simple way of testing if the generator has no items, like peek, hasNext, isEmpty, something along those lines?
Is there a simple way of testing if the generator has no items, like peek, hasNext, isEmpty, something along those lines?
Suggestion:
def peek(iterable):
try:
first = next(iterable)
except StopIteration:
return None
return first, itertools.chain([first], iterable)
Usage:
res = peek(mysequence)
if res is None:
# sequence is empty. Do stuff.
else:
first, mysequence = res
# Do something with first, maybe?
# Then iterate over the sequence:
for element in mysequence:
# etc.
The simple answer to your question: no, there is no simple way. There are a whole lot of work-arounds.
There really shouldn't be a simple way, because of what generators are: a way to output a sequence of values without holding the sequence in memory. So there's no backward traversal.
You could write a has_next function or maybe even slap it on to a generator as a method with a fancy decorator if you wanted to.
The best approach, IMHO, would be to avoid a special test. Most times, use of a generator is the test:
thing_generated = False
# Nothing is lost here. if nothing is generated,
# the for block is not executed. Often, that's the only check
# you need to do. This can be done in the course of doing
# the work you wanted to do anyway on the generated output.
for thing in my_generator():
thing_generated = True
do_work(thing)
If that's not good enough, you can still perform an explicit test. At this point, thing will contain the last value generated. If nothing was generated, it will be undefined - unless you've already defined the variable. You could check the value of thing, but that's a bit unreliable. Instead, just set a flag within the block and check it afterward:
if not thing_generated:
print "Avast, ye scurvy dog!"
Just fell on this thread and realized that a very simple and easy to read answer was missing:
def is_empty(generator):
for item in generator:
return False
return True
If we are not suppose to consume any item then we need to re-inject the first item into the generator:
def is_empty_no_side_effects(generator):
try:
item = next(generator)
def my_generator():
yield item
yield from generator
return my_generator(), False
except StopIteration:
return (_ for _ in []), True
Example:
>>> g=(i for i in [])
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
True
>>> g=(i for i in range(10))
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
False
>>> list(g)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
I hate to offer a second solution, especially one that I would not use myself, but, if you absolutely had to do this and to not consume the generator, as in other answers:
def do_something_with_item(item):
print item
empty_marker = object()
try:
first_item = my_generator.next()
except StopIteration:
print 'The generator was empty'
first_item = empty_marker
if first_item is not empty_marker:
do_something_with_item(first_item)
for item in my_generator:
do_something_with_item(item)
Now I really don't like this solution, because I believe that this is not how generators are to be used.
Prompted by Mark Ransom, here's a class that you can use to wrap any iterator so that you can peek ahead, push values back onto the stream and check for empty. It's a simple idea with a simple implementation that I've found very handy in the past.
class Pushable:
def __init__(self, iter):
self.source = iter
self.stored = []
def __iter__(self):
return self
def __bool__(self):
if self.stored:
return True
try:
self.stored.append(next(self.source))
except StopIteration:
return False
return True
def push(self, value):
self.stored.append(value)
def peek(self):
if self.stored:
return self.stored[-1]
value = next(self.source)
self.stored.append(value)
return value
def __next__(self):
if self.stored:
return self.stored.pop()
return next(self.source)
>>> gen = (i for i in [])
>>> next(gen)
Traceback (most recent call last):
File "<pyshell#43>", line 1, in <module>
next(gen)
StopIteration
At the end of generator StopIteration is raised, since in your case end is reached immediately, exception is raised. But normally you shouldn't check for existence of next value.
another thing you can do is:
>>> gen = (i for i in [])
>>> if not list(gen):
print('empty generator')
Sorry for the obvious approach, but the best way would be to do:
for item in my_generator:
print item
Now you have detected that the generator is empty while you are using it. Of course, item will never be displayed if the generator is empty.
This may not exactly fit in with your code, but this is what the idiom of the generator is for: iterating, so perhaps you might change your approach slightly, or not use generators at all.
I found only this solution as working for empty iterations as well.
def is_generator_empty(generator):
a, b = itertools.tee(generator)
try:
next(a)
except StopIteration:
return True, b
return False, b
is_empty, generator = is_generator_empty(generator)
Or if you do not want to use exception for this try to use
def is_generator_empty(generator):
a, b = itertools.tee(generator)
for item in a:
return False, b
return True, b
is_empty, generator = is_generator_empty(generator)
In the marked solution you are not able to use it for empty generators like
def get_empty_generator():
while False:
yield None
generator = get_empty_generator()
Just to try to help with my "2 cents", I am going to describe my experience:
I have a generator that I need slicing it using itertools.islice into small generators. Then to check if my sub generators are empty or not, I just convert/consume them to a small list and I check if the list is empty or not.
For example:
from itertools import islice
def generator(max_yield=10):
a = 0
while True:
a += 1
if a > max_yield:
raise StopIteration()
yield a
tg = generator()
label = 1
while True:
itg = list(islice(tg, 3))
if not itg: # <-- I check if the list is empty or not
break
for i in itg:
print(f'#{label} - {i}')
label += 1
Output:
#1 - 1
#1 - 2
#1 - 3
#2 - 4
#2 - 5
#2 - 6
#3 - 7
#3 - 8
#3 - 9
#4 - 10
Maybe this is not the best approach, mainly because it consumes the generator, however it works to me.
This is an old and answered question, but as no one has shown it before, here it goes:
for _ in generator:
break
else:
print('Empty')
There's a very simple solution: if next(generator,-1) == -1 then the generator is empty!
Inspecting the generator before iterating over it conforms to the LBYL coding style. Another approach (EAFP) would be to iterate over it and then check whether it was empty or not.
is_empty = True
for item in generator:
is_empty = False
do_something(item)
if is_empty:
print('Generator is empty')
This approach also handles well infinite generators.