Как узнать, пустой ли генератор с самого начала?

Есть ли простой способ тестирования, если у генератора нет элементов, таких как peek, hasNext, isEmpty, что-то вдоль этих строк?

Ответ 1

Простой ответ на ваш вопрос: нет, нет простого способа. Существует множество рабочих мест.

На самом деле не должно быть простого способа, из-за которого генераторы: способ вывода последовательности значений без сохранения последовательности в памяти. Таким образом, нет обратного обхода.

Вы могли бы написать функцию has_next или, возможно, даже пощекотать ее генератору как метод с фантастическим декоратором, если хотите.

Ответ 2

Предложение:

def peek(iterable):
    try:
        first = next(iterable)
    except StopIteration:
        return None
    return first, itertools.chain([first], iterable)

Использование:

res = peek(mysequence)
if res is None:
    # sequence is empty.  Do stuff.
else:
    first, mysequence = res
    # Do something with first, maybe?
    # Then iterate over the sequence:
    for element in mysequence:
        # etc.

Ответ 3

Простым способом является использование необязательного параметра для next(), который используется, если генератор исчерпан (или пуст). Например:

iterable = some_generator()

_exhausted = object()

if next(iterable, _exhausted) == _exhausted:
    print('generator is empty')

Изменить: Исправлена ​​проблема, отмеченная в комментарии mehtunguh.

Ответ 4

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

def do_something_with_item(item):
    print item

empty_marker = object()

try:
     first_item = my_generator.next()     
except StopIteration:
     print 'The generator was empty'
     first_item = empty_marker

if first_item is not empty_marker:
    do_something_with_item(first_item)
    for item in my_generator:
        do_something_with_item(item)

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

Ответ 5

Лучшим подходом, ИМХО, было бы избежать специального теста. В большинстве случаев использование генератора является тестом:

thing_generated = False

# Nothing is lost here. if nothing is generated, 
# the for block is not executed. Often, that the only check
# you need to do. This can be done in the course of doing
# the work you wanted to do anyway on the generated output.
for thing in my_generator():
    thing_generated = True
    do_work(thing)

Если это не достаточно хорошо, вы все равно можете выполнить явный тест. На этом этапе thing будет содержать последнее генерируемое значение. Если ничего не было создано, оно будет undefined - если вы уже не определили переменную. Вы можете проверить значение thing, но это немного ненадежно. Вместо этого просто установите флаг внутри блока и проверьте его позже:

if not thing_generated:
    print "Avast, ye scurvy dog!"

Ответ 6

next(generator, None) is not None

Или замените None, но независимо от того, что вы знаете, это не в вашем генераторе.

Изменить: Да, это пропустит 1 элемент в генераторе. Часто, однако, я проверяю, является ли генератор пустым только для целей проверки, но на самом деле его не используют. Или иначе я делаю что-то вроде:

def foo(self):
    if next(self.my_generator(), None) is None:
        raise Exception("Not initiated")

    for x in self.my_generator():
        ...

Ответ 7

Извините за очевидный подход, но наилучшим способом было бы сделать:

for item in my_generator:
     print item

Теперь вы обнаружили, что генератор пуст во время его использования. Конечно, элемент не будет отображаться, если генератор пуст.

Это может не соответствовать вашему коду, но это то, что идиома генератора для: итерации, поэтому, возможно, вы можете немного изменить свой подход или вообще не использовать генераторы.

Ответ 8

Я понимаю, что на этот пост это 5 лет, но я нашел его, ища идиоматический способ сделать это, и не видел, как мое решение опубликовано. Итак, для потомков:

import itertools

def get_generator():
    """
    Returns (bool, generator) where bool is true iff the generator is not empty.
    """
    gen = (i for i in [0, 1, 2, 3, 4])
    a, b = itertools.tee(gen)
    try:
        a.next()
    except StopIteration:
        return (False, b)
    return (True, b)

Конечно, как я уверен, многие комментаторы укажут, это хаки и работает только в определенных ограниченных ситуациях (например, когда генераторы свободны от побочных эффектов). YMMV.

Ответ 9

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

Здесь класс оболочки, который можно добавить к существующему итератору, чтобы добавить тест __nonzero__, чтобы вы могли видеть, что генератор пуст с помощью простого if. Возможно, его также можно превратить в декоратора.

class GenWrapper:
    def __init__(self, iter):
        self.source = iter
        self.stored = False

    def __iter__(self):
        return self

    def __nonzero__(self):
        if self.stored:
            return True
        try:
            self.value = self.source.next()
            self.stored = True
        except StopIteration:
            return False
        return True

    def next(self):
        if self.stored:
            self.stored = False
            return self.value
        return self.source.next()

Вот как вы его используете:

with open(filename, 'r') as f:
    f = GenWrapper(f)
    if f:
        print 'Not empty'
    else:
        print 'Empty'

Ответ 10

>>> gen = (i for i in [])
>>> next(gen)
Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    next(gen)
StopIteration

В конце генератора StopIteration поднимается, так как в вашем случае конец достигается немедленно, возникает исключение. Но обычно вам не следует проверять наличие следующего значения.

другое, что вы можете сделать:

>>> gen = (i for i in [])
>>> if not list(gen):
    print('empty generator')

Ответ 11

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

was_empty = True

for some_item in some_generator:
    was_empty = False
    do_something_with(some_item)

if was_empty:
    handle_already_empty_generator_case()

Ответ 12

Вот мой простой подход, который я использую, чтобы продолжать возвращать итератор, проверяя, что что-то было упущено Я просто проверяю, работает ли цикл:

        n = 0
        for key, value in iterator:
            n+=1
            yield key, value
        if n == 0:
            print ("nothing found in iterator)
            break

Ответ 13

Просто оберните генератор itertools.chain, поместите что-то, что будет представлять конец итерабельного, как второй итерируемый, а затем просто проверьте что.

Пример:

import itertools

g = some_iterable
eog = object()
wrap_g = itertools.chain(g, [eog])

Теперь все, что осталось, - проверить, добавлено ли это значение к концу итерабельного, когда вы его прочитаете, то это означает конец

for value in wrap_g:
    if value == eog: # DING DING! We just found the last element of the iterable
        pass # Do something

Ответ 14

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

def generator_or_none(func):
    """Wrap a generator function, returning None if it empty. """

    def inner(*args, **kwargs):
        # peek at the first item; return None if it doesn't exist
        try:
            next(func(*args, **kwargs))
        except StopIteration:
            return None

        # return original generator otherwise first item will be missing
        return func(*args, **kwargs)

    return inner

Использование:

import random

@generator_or_none
def random_length_generator():
    for i in range(random.randint(0, 10)):
        yield i

gen = random_length_generator()
if gen is None:
    print('Generator is empty')

Один пример, где это полезно, - это код шаблонов - т.е. jinja2

{% if content_generator %}
  <section>
    <h4>Section title</h4>
    {% for item in content_generator %}
      {{ item }}
    {% endfor %
  </section>
{% endif %}

Ответ 15

с помощью islice вам нужно только проверить первую итерацию, чтобы узнать, пуст ли она.

из itertools import islice

def isempty (iterable):
    return list (islice (iterable, 1)) == []

Ответ 16

В моем случае мне нужно было знать, был ли заполнен массив генераторов, прежде чем передать его функции, которая объединила элементы, т.е. zip(...). Решение аналогично, но достаточно различно, из принятого ответа:

Определение:

def has_items(iterable):
    try:
        return True, itertools.chain([next(iterable)], iterable)
    except StopIteration:
        return False, []

Использование:

def filter_empty(iterables):
    for iterable in iterables:
        itr_has_items, iterable = has_items(iterable)
        if itr_has_items:
            yield iterable


def merge_iterables(iterables):
    populated_iterables = filter_empty(iterables)
    for items in zip(*populated_iterables):
        # Use items for each "slice"

Моя конкретная проблема имеет свойство, что итераторы либо пусты, либо имеют точно такое же количество записей.

Ответ 17

Как насчет использования any()? Я использую его с генераторами, и он работает нормально. Здесь есть парень, объясняющий немного об этом

Ответ 18

Я решил это, используя функцию sum. Ниже приведен пример, который я использовал с glob.iglob(который возвращает генератор).

def isEmpty():
    files = glob.iglob(search)
    if sum(1 for _ in files):
        return True
    return False

* Это, вероятно, не будет работать для HUGE-генераторов, но должно прекрасно работать для небольших списков