Как я узнаю, что генератор пуст с самого начала? - PullRequest
109 голосов
/ 19 марта 2009

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

Ответы [ 21 ]

81 голосов
/ 20 марта 2009

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

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.
46 голосов
/ 19 марта 2009

Простой ответ на ваш вопрос: нет, простого пути не существует. Есть много обходных путей.

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

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

23 голосов
/ 03 февраля 2014

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

iterable = some_generator()

_exhausted = object()

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

Редактировать: Исправлена ​​проблема, указанная в комментарии Мехтунгу.

9 голосов
/ 22 мая 2016

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

То есть, это работает, если ваш генератор происходит от функции , как в generator().

9 голосов
/ 19 марта 2009

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

thing_generated = False

# Nothing is lost here. if nothing is generated, 
# the for block is not executed. Often, that's 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 будет содержать последнее сгенерированное значение. Если ничего не было сгенерировано, оно будет неопределенным - если вы уже не определили переменную. Вы можете проверить значение thing, но это ненадежно. Вместо этого просто установите флаг в блоке и проверьте его позже:

if not thing_generated:
    print "Avast, ye scurvy dog!"
8 голосов
/ 19 марта 2009

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

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)

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

3 голосов
/ 13 июня 2014

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

Вот класс-оболочка, который можно добавить к существующему итератору для добавления теста __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 = next(self.source)
            self.stored = True
        except StopIteration:
            return False
        return True

    def __next__(self):  # use "next" (without underscores) for Python 2.x
        if self.stored:
            self.stored = False
            return self.value
        return next(self.source)

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

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

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

3 голосов
/ 23 марта 2014

Я понимаю, что этому посту уже 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.

3 голосов
/ 19 марта 2009

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

for item in my_generator:
     print item

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

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

1 голос
/ 20 марта 2019

Просто упал в эту ветку и понял, что очень простой и легко читаемый ответ отсутствует:

def is_empty(generator):
    for item in generator:
        return False
    return True

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

def is_empty_no_side_effects(generator):
    try:
        item = next(generator)
        def my_generator():
            yield item
            yield from generator
        return my_generator(), False
    except StopIteration:
        return (_ for _ in []), True

Пример:

>>> g=(i for i in [])
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
True
>>> g=(i for i in range(10))
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
False
>>> list(g)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...