views:

456

answers:

7

Is there a simple way of testing if the generator has no items, like peek, hasNext, isEmpty, something along those lines?

A: 
>>> 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')
SilentGhost
Which does actually consume the whole generator. Sadly, it's not clear from the question if this is desirable or undesirable behavior.
S.Lott
as any other way of "touching" generator, I suppose.
SilentGhost
+2  A: 

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.

Ali A
Or... questioner could provide some hint as to *why* one would try to detect an empty generator?
S.Lott
did you mean "nothing will be displayed since generator is empty"?
SilentGhost
S.Lott. I agree. I can't see why. But I think even if there was a reason, the problem might be better turned to use each item instead.
Ali A
+2  A: 

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.

Ali A
+3  A: 

Here is a recipe for an iterator wrapper, it probably allows to do what you want:

http://code.activestate.com/recipes/502304/

Note: I have not tested if it works or not. Nor am I sure that the functionality is useful.

theller
+1  A: 

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
for thing in my_generator():
    thing_generated = True
    print "I've generated something!"

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!"
vezult
+2  A: 

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.

David Berger
fair enough, that makes sense. i knew there was no way of finding the length of a generator, but thought i might have missed a way of finding if it is initially going to generate anything at all.
Dan
Oh, and for reference, I tried implementing my own "fancy decorator" suggestion. HARD. Apparently copy.deepcopy doesn't work on generators.
David Berger
You can't find the length of a generator -- you can only run the generator, and generate the entire sequence in memory and see how long the sequence turned out to be.
S.Lott
A: 

Suggestion:

def peek(iterable):
    try:
        first, rest = next(iterable)
    except StopIteration:
        return None
    return first, itertools.chain([first], rest)

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.
John Fouhy