Welcome, guest | Sign In | My Account | Store | Cart

This small recipe enables truth value testing on iterables.

It is quite common to do things like:

if somesequence:
    ...
else:
    ...

Such constructs, that enter the if block if the sequence's got one or more elements and the else block if it's empty, work fine on non-lazy builtin sequences (lists, strings, tuples) and dictionaries as well, but doesn't necessarily work on generic iterables - most of them are always true regardless of their contents, since they're some kind of object. A classical example is generators, but such behaviour can be extended to any object implementing the Iterable interface.

Just wrap your iterable with this decorator and you'll get a truth-aware iterable which supports proper truth testing by doing a small first element prefetching and can then be used just like the original iterable.

Python, 29 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from itertools import chain

class TruthValueAwareIterable(object):
    def __init__(self, iterable):
        self._iterator = iter(iterable)
        try:
            self._head = [self._iterator.next()]
            self._has_value = True
        except StopIteration:
            self._head = []
            self._has_value = False

    def __nonzero__(self):
        return self._has_value

    def __iter__(self):
        return chain(self._head, self._iterator)

if __name__ == "__main__":
    def integer_generator():
        yield 1
        yield 2
        yield 3

    assert not TruthValueAwareIterable(iter([]))
    assert TruthValueAwareIterable(integer_generator())

    assert list(TruthValueAwareIterable([])) == []
    assert list(TruthValueAwareIterable(integer_generator())) == [1, 2, 3]

5 comments

pavel 10 years, 10 months ago  # | flag

I like it.

which works fine on lists, but doesn't work on iterables

s/iterables/generators/g List is also an iterable. You mean generator, it's truth value is True despite it produces empty sequence.

pavel 10 years, 10 months ago  # | flag

As far I know in python 3.3 you could use

def __iter__(self):
    yield self._head # concrete value
    yield from self._iterator
Alan Franzoni (author) 10 years, 10 months ago  # | flag

pavel: I mean what I wrote, you seem to look at it the other way round :-)

The iterable interface is generic http://docs.python.org/2/library/collections.html?highlight=collections#collections.Iterable . Lists, generators, strings and tuples, all of those are iterables.

The normal truth value testing works only on lists, not on just any iterable.

As for the other syntax, this recipe is designed to work mostly on Python 2.x, I hadn't even thought of 3.x.

Alan Franzoni (author) 10 years, 10 months ago  # | flag

pavel: by the way I've updated my description and I hope it's better now, check it out.

Thanks!

pavel 10 years, 10 months ago  # | flag

Hi Alan, your're right, please ignore my first comment