20 ответов:
простой ответ на ваш вопрос: нет, нет простого способа. Есть много обходных путей.
там действительно не должно быть простого способа, из-за того, что генераторы: способ вывода последовательности значений не удерживая последовательность в памяти. Таким образом, нет обратного обхода.
вы можете написать функцию has_next или, возможно, даже шлепнуть ее на генератор в качестве метода с причудливым декоратором, если хотите.
предложение:
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.
простой способ-использовать необязательный параметр для next () который используется, если генератор исчерпан (или пуст). Например:
iterable = some_generator() _exhausted = object() if next(iterable, _exhausted) == _exhausted: print('generator is empty')редактировать: Исправлена проблема, на которую указал в комментарии mehtunguh по.
лучший подход, ИМХО, было бы избежать специального теста. В большинстве случаев, использование генератора и тест:
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!"
next(generator, None) is not Noneи заменить
Noneно какое бы значение вы ни знали, это не в свой генератор.Edit: Да, это пропустит 1 пункт в генераторе. Однако часто я проверяю, пуст ли генератор только для целей проверки, а затем не использую его. Или иначе я делаю что-то типа:
def foo(self): if next(self.my_generator(), None) is None: raise Exception("Not initiated") for x in self.my_generator(): ...то есть, это работает, если ваш генератор из функции, как в
generator().
Я ненавижу предлагать второе решение, особенно то, которое я бы не использовал сам, но, если вы абсолютно С чтобы сделать это и не потреблять генератор, как в других ответах:
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)теперь мне очень не нравится это решение, потому что я считаю, что это не так, как Генераторы будут использоваться.
извините за очевидный подход, но лучший способ было бы сделать:
for item in my_generator: print itemтеперь вы обнаружили, что генератор пуст, пока вы его используете. Конечно, элемент никогда не будет отображаться, если генератор пуст.
Это может не совсем соответствовать вашему коду, но это то, для чего предназначена идиома генератора: итерация, поэтому, возможно, вы можете немного изменить свой подход или вообще не использовать генераторы.
Я понимаю, что этому сообщению на данный момент 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)конечно, как я уверен, что многие комментаторы укажут, это хаки и работает только в определенных ограниченных ситуациях (где генераторы не имеют побочных эффектов, например). МММ.
все, что вам нужно сделать, чтобы увидеть, если генератор пуста, чтобы попытаться получить следующий результат. Конечно, если ты не готов чтобы использовать этот результат, то вы должны сохранить его, чтобы вернуть его снова позже.
вот класс-оболочка, который можно добавить в существующий итератор, чтобы добавить
__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'обратите внимание, что вы можете проверить на пустоты в любое время, а не только в начале итерации.
>>> 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')
в моем случае мне нужно было знать, было ли заполнено множество генераторов, прежде чем я передал его в функцию, которая объединила элементы, т. е.
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"моя конкретная проблема имеет свойство, что iterables либо пусты, либо имеют точно такое же количество записей.
Если вам нужно знать до вы используете генератор, то нет, нет простого способа. Если вы можете подождать, пока после вы использовали генератор, есть простой способ:
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()
вот мой простой подход, который я использую, чтобы продолжать возвращать итератор при проверке, если что-то было получено Я просто проверяю, работает ли цикл:
n = 0 for key, value in iterator: n+=1 yield key, value if n == 0: print ("nothing found in iterator) break
вот простой декоратор, который обертывает генератор, поэтому он возвращает None, если он пуст. Это может быть полезно, если ваш код должен знать, будет ли генератор производить что-нибудь до цикл через него.
def generator_or_none(func): """Wrap a generator function, returning None if it's 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 %}
просто оберните генератор с itertools.цепь, поместите что-то, что будет представлять конец iterable как второй iterable, а затем просто проверьте это.
Ex:
import itertools g = some_iterable eog = object() wrap_g = itertools.chain(g, [eog])теперь все, что осталось, это проверить это значение, которое мы добавили в конец iterable, когда вы его прочитаете, тогда это будет означать конец
for value in wrap_g: if value == eog: # DING DING! We just found the last element of the iterable pass # Do something
с помощью islice вам нужно только проверить до первой итерации, чтобы обнаружить, если он пуст.
из itertools import islice
def isempty (iterable):
возвращаемый список (islice (iterable,1)) == []
Как насчет использования любого ()? Я использую его с генератора и он работает нормально. здесь есть парень, объясняющий немного об этом
использовать peek функция в cytoolz.
from cytoolz import peek from typing import Tuple, Iterable def is_empty_iterator(g: Iterable) -> Tuple[Iterable, bool]: try: _, g = peek(g) return g, False except StopIteration: return g, Trueитератор, возвращаемый этой функцией, будет эквивалентен исходному, переданному в качестве аргумента.
подсказанный Mark Ransom, вот класс, который вы можете использовать для обертывания любого итератора, чтобы вы могли заглянуть вперед, вернуть значения в поток и проверить пустоту. Это простая идея с простой реализацией, которую я нашел очень удобной в прошлом.
class Pushable: def __init__(self, iter): self.source = iter self.stored = [] def __iter__(self): return self def __bool__(self): if self.stored: return True try: self.stored.append(next(self.source)) except StopIteration: return False return True def push(self, value): self.stored.append(value) def peek(self): if self.stored: return self.stored[-1] value = next(self.source) self.stored.append(value) return value def __next__(self): if self.stored: return self.stored.pop() return next(self.source)
Я решил ее с помощью функции sum. См. ниже пример, который я использовал с glob.iglob (который возвращает генератор).
def isEmpty(): files = glob.iglob(search) if sum(1 for _ in files): return True return False*Это, вероятно, не будет работать для огромных генераторов, но должно хорошо работать для небольших списков
Comments