Проверьте, действительно ли генератор генерирует что-то в одной строке - PullRequest
2 голосов
/ 15 марта 2012

Многие функции библиотеки, которые я использую, возвращают generator s вместо list s, когда результатом является некоторая коллекция.

Иногда я просто хочу проверить, пуст ли результат или нет, и, конечно, я не могу написать что-то вроде:

   result = return_generator()
   if result:
      print 'Yes, the generator did generate something!'

Теперь я придумаю однострочник, который решает эту проблему без использования генератора:

   result = return_generator()
   if zip("_", result):
      print 'Yes, the generator did generate something!'

Интересно, есть ли более понятные способы решения этой проблемы в одну строку ?

Ответы [ 4 ]

4 голосов
/ 15 марта 2012

вот один из способов, использующий функцию itertools tee, которая дублирует генератор:

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
3 голосов
/ 15 марта 2012

Этот zip материал съедает первый полученный предмет, так что это тоже не очень хорошая идея.

Вы можете определить, есть ли у генератора предмет, уступающий ему, получив и сохранив его, пока он не понадобится. Следующий класс поможет вам сделать это.

При необходимости он получает элемент от итератора и сохраняет его.

Если запросить пустоту (if myiterwatch: ...), он пытается получить и возвращает, если может получить.

Если запросить следующий элемент, он вернет найденный или новый.

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

Это решает проблему очень общим образом. Если у вас есть итератор, который вы просто хотите протестировать, вы можете использовать

guard = object()
result = return_generator()
if next(result, guard) is not guard:
    print 'Yes, the generator did generate something!'

next(a, b) возвращает b, если итератор a исчерпан. Поэтому, если он возвращает охрану в нашем случае, он не генерирует что-либо, в противном случае он генерировал.

Но ваш zip() подход также вполне допустим ...

2 голосов
/ 15 марта 2012

Вместо того, чтобы вернуть генератор, просто использовать его?Вот пример генератора, который может возвращать или не возвращать результаты - если есть результаты, выполняется действие, если результатов нет, действие не предпринимается.

1 голос
/ 15 марта 2012

Вот два наиболее простых решения для вашей ситуации, о которых я могу подумать:

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