views:

197

answers:

8

Suppose that I am looping over a iterable and would like to take some action if the iterator is empty. The two best ways that I can think of to do this are

for i in iterable:
     # do_something
if not iterable:
    # do_something_else

and

empty = True
for i in iterable:
    empty = False
    # do_something
if empty:
    # do_something_else

The first depends on the the iterable being a collection (so useless for when the iterable gets passed into the function/method where the loop is) and the second sets empty on every pass through the loop which seems ugly.

Is there another way that I'm missing or is the second alternative the best? It would be really cool if there was some clause that I could add to the loop statement that would handle this for me much like else makes not_found flags go away.


[Deleted specific example because it seems to be confusing people]


I am not looking for clever hacks.

I am not looking for solutions that involve a lot of code

I am looking for a simple language feature. I am looking for a clear and pythonic way to iterate over an iterable and take some action if the iterable is empty that any experienced python programmer will be understand. If I could do it without setting a flag on every iteration, that would be fantastic. If there is no simple idiom that does this, then forget about it.

+2  A: 

This is quite hackish, but you can delete i and then check if it exists after the loop (if not, the loop never happened):

try:
    del i
except NameException: pass

for i in iterable:
    do_something(i)

try:
    del i
except NameException:
    do_something_else()

I think that's probably uglier than just using a flag though

Michael Mrozek
That's clever but as you anticipated with your last statement, It's not quite what I'm looking for. That being said, cleverness always deserves a +1
aaronasterling
+2  A: 

Update 2

I liked Odomontois' answer. IMHO it is better suited to this problem than what I have written below.

Update

(After reading the OP's comment and edited question) You can do that too. See below:

def with_divisible(n, a, b, f):
 it = (i for i in xrange(a, b) if not i % n)
 for i in wrapper(it):
  f(i)

>>> with_divisible(1, 1, 1, lambda x: x)
Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    with_divisible(1, 1, 1, lambda x: x)
  File "<pyshell#54>", line 3, in with_divisible
    for i in wrapper(it):
  File "<pyshell#46>", line 4, in wrapper
    raise EmptyIterableException("Empty")
EmptyIterableException: Empty

>>> with_divisible(7, 1, 21, lambda x: x)
7
14
...Snipped...
    raise EmptyIterableException("Empty")
EmptyIterableException: Empty

Original Answer

Interesting problem. I did some experiments and came up with the following:

class EmptyIterableException(Exception):
    pass

def wrapper(iterable):
    for each in iterable:
        yield each
    raise EmptyIterableException("Empty")

try:
    for each in wrapper(iterable):
        do_something(each)
except EmptyIterableException, e:
    do_something_else()
Manoj Govindan
sorry, i worded my question poorly. I'm going to edit my question.
aaronasterling
and I messed up my edit as well. Although, that shouldn't have thrown you off. the line in the `for` loop reads `empty = False`. your code raises the exception regardless. It works though if I move the `empty` flag into the wrapper and raise the exception on `if empty` within the wrapper. Sorry I'm being so picky, but I'd hardly call the solution idiomatic. I did upvote it though.
aaronasterling
@aaronasterling: Your expression returns a generator object. Will it help to call `next()` on the generator and trap the `StopIteration` raised?
Manoj Govindan
I don't think so - I loose the first value of the generator and it's only a generator expression in this one example that I cooked up. I'm trying to find a general way to code loops like this. Traversing a directed graph and taking some action on the sinks would be another example.
aaronasterling
@aaronasterling: got it. On a side note, if you are working with lists you can use `if bool(my_list)` to check if the list is empty or not. I don't know how to do that with generators.
Manoj Govindan
you can just do `if my_list` to check if a list is empty ;)
aaronasterling
@aaronasterling: aye aye. You are correct of course.
Manoj Govindan
A: 

What about reversing "if" and "for":

if iterable:
    for i in iterable:
        do_something(i)
else:
    do_something_else()

OK, this does not work!

Here is an other solution: http://code.activestate.com/recipes/413614-testing-for-an-empty-iterator/

Eike
try `iterable = iter([])`.
KennyTM
What KennyTM said. A straight forward boolean test will fail with generators.
Manoj Govindan
Ah... thanks, I was wondering why everyone wrote such complicated code.
Eike
-1. I originally let it slide but somebody upvoted it and this answer just doesn't deserve to have a +1. Sorry for being honest.
aaronasterling
+2  A: 
if not map(do_something_callable,iterable) : 
    # do something else
Odomontois
How does that even begin to address the question?
aaronasterling
Actually that might be worth a try. The map call `map(f,divisibles(n,a,b))` will map the function `f` over the sequence `divisibles`. If `divisibles` was empty to start with (I believe this is what you were trying to test) the result of `map` will be an empty list and therefore line#2 will raise an exception. If however `divisibles` is non empty, `map` will produce the values of `f` mapped over `divisibles`.
Manoj Govindan
No this is not worth a try. It works great for the particular example that I posted. It is useless in the broader case that I am asking about. Please delete this answer.
aaronasterling
but that only works if I define 'do something' as a function. Again, I am interested in a general idiom. I am well aware of the uses of `map` but it is not always practical.
aaronasterling
A: 

This is a combination of Michael Mrozek's and FM's answers:

def with_divisible(n, a, b, f):
    '''apply f to every integer x such that n divides x and a <= x < b'''
    it = (i for i in xrange(a, b) if not i % n)
    for i in it:
        f(i)
    try: i            # test if `it` was empty
    except NameError: print('do something else')

def g(i):
    print i,

with_divisible( 3, 1, 10, g)   # Prints 3 6 9.
with_divisible(33, 1, 10, g)   # Prints "do something else"
unutbu
+2  A: 

I think this the the cleanest way to do this:

# first try with exceptions
def nonempty( iter ):
    """ returns `iter` if iter is not empty, else raises TypeError """
    try:
        first = next(iter)
    except StopIteration:
        raise TypeError("Emtpy Iterator")
    yield first
    for item in iter:
        yield item


# a version without exceptions. Seems nicer:
def isempty( iter ):
    """ returns `(True, ())` if `iter` if is empty else `(False, iter)`
         Don't use the original iterator! """
    try:
        first = next(iter)
    except StopIteration:
        return True, ()
    else:
        def iterator():
            yield first
            for item in iter:
                yield item
        return False, iterator()



for x in ([],[1]):
    # first version
    try:
        list(nonempty(iter(x))) # trying to consume a empty iterator raises
    except TypeError:
        print x, "is empty"
    else:
        print x, "is not empty"

    # with isempty
    empty, it = isempty(iter(x))
    print x,  "is", ("empty" if empty else "not empty")
THC4k
+1. that's the best answer so far. I'll probably end up accepting this one.
aaronasterling
A: 

Generators have a 'gi_frame' property which is None once the generator is exhausted, but only after StopIteration has been raised. If that's acceptable, here's something you could try:

import types

def do(x, f, f_empty):
    if type(x) == types.GeneratorType:
        # generators have a 'gi_frame' property,
        # which is None once the generator is exhausted
        if x.gi_frame:
            # not empty
            return f(x)
        return f_empty(x)
    if x:
        return f(x)
    return f_empty(x)

def nempty(lst):
    print lst, 'not empty'

def empty(lst):
    print 'Twas empty!'

# lists
do([2,3,4], nempty, empty)
do([], nempty, empty)

# generators
do((i for i in range(5)), nempty, empty)
gen = (i for i in range(1))
gen.next()
try:
    gen.next()
except StopIteration:
    pass
do(gen, nempty, empty)
nilamo
+1  A: 

The general way forward if an iterator is to be partially checked before being consumed is to use itertools.tee. This way we can have two copies of the iterator and check one for emptiness while still consuming the other copy from the start.

from itertools import tee
it1, it2 = tee(iterable)
try:
    it1.next()
    for i in it2:
        do_some_action(i) #iterator is not empty
except StopIteration:
    do_empty_action() #iterator is empty

The StopIteration exception is bound to be a result of the call to it1.next(), as any StopIteration exceptions raised froom inside the loop will terminate that loop.

Edit: for those who don't like such exceptions, islice can be used to set up a single step loop:

from itertools import tee, islice
it1, it2 = tee(iterable)
for _ in islice(it1, 1):
    #loop entered if iterator is not empty
    for i in it2:
        do_some_action(i)
    break #if loop entered don't execute the else section
else:
    do_empty_action()

I personally prefer the first style. YMMV.

Muhammad Alkarouri