Question

I'm a big fan of Python's for...else syntax - it's surprising how often it's applicable, and how effectively it can simplify code.

However, I've not figured out a nice way to use it in a generator, for example:

def iterate(i):
    for value in i:
        yield value
    else:
        print 'i is empty'

In the above example, I'd like the print statement to be executed only if i is empty. However, as else only respects break and return, it is always executed, regardless of the length of i.

If it's impossible to use for...else in this way, what's the best approach to this so that the print statement is only executed when nothing is yielded?

Was it helpful?

Solution

You're breaking the definition of a generator, which should throw a StopIteration exception when iteration is complete (which is automatically handled by a return statement in a generator function)

So:

def iterate(i):
    for value in i:
        yield value
    return

Best to let the calling code handle the case of an empty iterator:

count = 0
for value in iterate(range([])):
    print value
    count += 1
else:
    if count == 0:
        print "list was empty"

Might be a cleaner way of doing the above, but that ought to work fine, and doesn't fall into any of the common 'treating an iterator like a list' traps below.

OTHER TIPS

There are a couple ways of doing this. You could always use the Iterator directly:

def iterate(i):
    try:
        i_iter = iter(i)
        next = i_iter.next()
    except StopIteration:
        print 'i is empty'
        return

    while True:
        yield next
        next = i_iter.next()

But if you know more about what to expect from the argument i, you can be more concise:

def iterate(i):
    if i:  # or if len(i) == 0
        for next in i:
            yield next
    else:
        print 'i is empty'
        raise StopIteration()

Summing up some of the earlier answers, it could be solved like this:

def iterate(i):
    empty = True
    for value in i:
        yield value
        empty = False

    if empty:
        print "empty"

so there really is no "else" clause involved.

As you note, for..else only detects a break. So it's only applicable when you look for something and then stop.

It's not applicable to your purpose not because it's a generator, but because you want to process all elements, without stopping (because you want to yield them all, but that's not the point).

So generator or not, you really need a boolean, as in Ber's solution.

If it's impossible to use for...else in this way, what's the best approach to this so that the print statement is only executed when nothing is yielded?

Maximum i can think of:


>>> empty = True
>>> for i in [1,2]:
...     empty = False
... if empty:
...     print 'empty'
...
>>>
>>>
>>> empty = True
>>> for i in []:
...     empty = False
... if empty:
...    print 'empty'
...
empty
>>>

What about simple if-else?

def iterate(i):
    if len(i) == 0: print 'i is empty'
    else:
        for value in i:
            yield value
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top