Reputation: 33225
Many functions of a library I use return generator
s instead of list
s when the result is some collection.
Sometimes I just want to check if the result is empty of not, and of course I can't write something like:
result = return_generator()
if result:
print 'Yes, the generator did generate something!'
Now I come up with a one-liner that solve this problem without consuming the generator:
result = return_generator()
if zip("_", result):
print 'Yes, the generator did generate something!'
I wonder if there are cleaner ways to solve this problem in one line?
Upvotes: 2
Views: 448
Reputation: 17173
here is one way using itertools tee function which duplicates the generator:
from itertools import tee
a, b = tee(xrange(0))
try:
next(a)
print list(b)
except StopIteration:
print "f1 was empty"
a, b = tee(xrange(3))
try:
next(a)
print list(b)
except StopIteration:
print "f2 was empty"
>>>
[0, 1, 2, 3]
f2 was empty
Upvotes: 4
Reputation: 17173
Here are the two most simple solutions for your situation that I can think of:
def f1():
return (i for i in range(10))
def f2():
return (i for i in range(0))
def has_next(g):
try:
from itertools import chain
return chain([g.next()],g)
except StopIteration:
return False
g = has_next(f1())
if g:
print list(g)
g = has_next(f2())
if g:
print list(g)
def has_next(g):
for i in g:
return chain([i],g)
return False
g = has_next(f1())
if g:
print list(g)
g = has_next(f2())
if g:
print list(g)
Upvotes: 1
Reputation: 91029
This zip
stuff eats the first item yielded, so it is not a good idea as well.
You can only detect if a generator has an item yielding by getting and keeping it until needed. The following class will help you to do so.
If needed, it gets an item from the iterator and keeps it.
If asked for emptyness (if myiterwatch: ...
), it tries to get and returns if it could get one.
If asked for the next item, it will return the retrieved one or a new one.
class IterWatch(object):
def __init__(self, it):
self.iter = iter(it)
self._pending = []
@property
def pending(self):
try:
if not self._pending:
# will raise StopIteration if exhausted
self._pending.append(next(self.iter))
except StopIteration:
pass # swallow this
return self._pending
def next(self):
try:
return self.pending.pop(0)
except IndexError:
raise StopIteration
__next__ = next # for Py3
def __iter__(self): return self
def __nonzero__(self):
# returns True if we have data.
return not not self.pending
# or maybe bool(self.pending)
__bool__ = __nonzero__ # for Py3
This solves the problem in a very generic way. If you have an iterator which you just want to test, you can use
guard = object()
result = return_generator()
if next(result, guard) is not guard:
print 'Yes, the generator did generate something!'
next(a, b)
returns b
if iterator a
is exhausted. So if it returns the guard in our case, it didn't generate something, otherwise it did.
But your zip()
approach is perfectly valid as well...
Upvotes: 3
Reputation: 28036
Instead of returning the generator, just use it? Here's an example of a generator that may or may not return results - if there are results action is taken, if there are no results no action is taken.
#!/usr/bin/python
import time
def do_work():
if int(time.time()) % 2:
for a in xrange(0,100):
yield a
for thing in do_work():
print thing
Upvotes: 2