Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I know if a generator is empty from the start?

People also ask

What is a generator expression Python?

A generator expression is an expression that returns a generator object. Basically, a generator function is a function that contains a yield statement and returns a generator object.

How does yield work in Python?

What Is Yield In Python? The Yield keyword in Python is similar to a return statement used for returning values or objects in Python. However, there is a slight difference. The yield statement returns a generator object to the one who calls the function which contains yield, instead of simply returning a value.

Is Python a generator?

Python provides a generator to create your own iterator function. A generator is a special type of function which does not return a single value, instead, it returns an iterator object with a sequence of values. In a generator function, a yield statement is used rather than a return statement.


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.


A simple way is to use the optional parameter for next() which is used if the generator is exhausted (or empty). For example:

iterable = some_generator()

_exhausted  = object()

if next(iterable, _exhausted) is _exhausted:
    print('generator is empty')

next(generator, None) is not None

Or replace None but whatever value you know it's not in your generator.

Edit: Yes, this will skip 1 item in the generator. Often, however, I check whether a generator is empty only for validation purposes, then don't really use it. Or otherwise I do something like:

def foo(self):
    if next(self.my_generator(), None) is None:
        raise Exception("Not initiated")

    for x in self.my_generator():
        ...

That is, this works if your generator comes from a function, as in generator().


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!"

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.


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]