Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way of taking action on attempt to loop over an empty iterable

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.


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.

like image 582
aaronasterling Avatar asked Aug 15 '10 04:08

aaronasterling


People also ask

Which words are used for iterations in Python?

Definite iteration loops are frequently referred to as for loops because for is the keyword that is used to introduce them in nearly all programming languages, including Python. Historically, programming languages have offered a few assorted flavors of for loop.

How do you make a loop loop a certain number of time in Python?

To loop through a set of code a specified number of times, we can use the range() function, The range() function returns a sequence of numbers, starting from 0 by default, and increments by 1 (by default), and ends at a specified number.

How do I know if my iterator is empty?

The best way to do that is with a peekable from more_itertools . from more_itertools import peekable iterator = peekable(iterator) if iterator: # Iterator is non-empty. else: # Iterator is empty. Just beware if you kept refs to the old iterator, that iterator will get advanced.

What is meant by iteration in Python?

An iterator is an object that contains a countable number of values. An iterator is an object that can be iterated upon, meaning that you can traverse through all the values. Technically, in Python, an iterator is an object which implements the iterator protocol, which consist of the methods __iter__() and __next__() .


2 Answers

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")
like image 82
Jochen Ritzel Avatar answered Sep 22 '22 19:09

Jochen Ritzel


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

like image 35
Michael Mrozek Avatar answered Sep 22 '22 19:09

Michael Mrozek