views:

1210

answers:

6

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 expression, 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?

+7  A: 

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.

Triptych
return is implied at the end of a generator. No need to include it.
recursive
I was thinking that, but I thought I'd leave it explicit here.
Triptych
+1: the "print i is empty" is someone else's problem, not the generator's.
S.Lott
This doesn't answer the question at all, does it? The question is how to execute some code in a generator function if the object you're iterating over doesn't contain any objects.
Chris B.
@Chris B: The question reflects something not very Pythonic. Rather than compound the problem with obscure code, it is often better to provide a more pythonic solution.
S.Lott
+1 let the code that calls your generator handle printing error messages by handling the StopIteration exception.
Soviut
@S. Lott: and what if you need to write a utility function that handles a complex error-correction routine if the iterator you have is empty? And you need to call it dozens of times from your code?
Chris B.
@Chris B.: Don't get the scenario -- perhaps you should put some demo code together and ask it as a separate question. The "empty iterators causing problems everywhere" sounds like a serious design problem, not a quick hack.
S.Lott
@S.Lott--if you don't understand the scenario, than you must accept there are probably some use cases for which a function which takes an iterator and either returns it or executes some arbitrary code if it is empty is both simple and elegant.
Chris B.
@Chris B: There don't appear to be any examples of such a function that are simple and elegant. Nor does there seem to be any use case for such a function. Hence my upvote on this answer -- it's the caller's problem.
S.Lott
@Chris B. what bothers me about your scenario is that you would have to write a generator with significant side effects, which to me feels quite unpythonic.
Triptych
@Triptych--what bothers me about your answer is that you would have to litter your code with the exact same snippet everywhere you used the generator, which to me feels quite unpythonic. Generators are exactly equivalent to functions; I don't know why you think they need to be "special".
Chris B.
I actually agree with Triptych, on reflection. Chris B: generators may be syntactically identical to functions (apart from the yields, obviously), but them having significant side effects does seem *wrong*, somehow. Is there any official guidance to support that assertion?
Alabaster Codify
The use case which prompted this question was simple enough to be answered by the "raise StopIteration" solution, and anything not in that category probably shouldn't be a simple generator.
Alabaster Codify
this has got to be my most controversial accepted answer ever.
Triptych
As noted in other answers, the else clause of for loops doesn't behave like this - the else suite will be executed even if there are items in the iterator. Also, the iterate generator does nothing. It's essentially the identity function of generators.
wilberforce
A: 

What about simple if-else?

def iterate(i):
    if len(i) == 0: print 'i is empty'
    else:
        for value in i:
            yield value
Martin Janiczek
The argument i might not have a defined len, so this is problematic.
Kiv
Same problem as recursive's answer, more or less.
Triptych
+4  A: 

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()
Chris B.
A: 

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

Mykola Kharechko
+3  A: 

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.

Ber
+2  A: 

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.

Beni Cherniavsky-Paskin