Поправьте меня, если я ошибаюсь, но если бы вы могли сделать действительно универсальное решение для любого генератора, это было бы эквивалентно установке точек останова в операторах yield и возможности «шагнуть назад». Означает ли это клонирование фрейма стека на выходах и их восстановление в StopIteration?
Ну, я думаю, восстановить их StopIteration или нет, но, по крайней мере, StopIteration скажет вам, что он пуст. Да, мне нужно поспать ...
4
Думаю, я знаю, почему он этого хочет. Если вы занимаетесь веб-разработкой с помощью шаблонов и передаете возвращаемое значение в шаблон, например Cheetah или что-то в этом роде, пустой список []удобно считать ложным, так что вы можете выполнить его проверку if и выполнить специальное поведение для чего-то или ничего. Генераторы верны, даже если они не дают никаких элементов.
jpsimons 08
1
Вот мой вариант использования ... Я использую glob.iglob("filepattern")предоставленный пользователем шаблон подстановки и хочу предупредить пользователя, если шаблон не соответствует ни одному файлу. Конечно, я могу обойти это разными способами, но полезно иметь возможность чисто проверить, оказался ли итератор пустым или нет.
Простой ответ на ваш вопрос: нет, простого пути нет. Есть много способов обхода.
На самом деле не должно быть простого способа из-за того, что такое генераторы: способа вывода последовательности значений без сохранения последовательности в памяти . Так что обратного обхода нет.
Вы можете написать функцию has_next или, может быть, даже прикрепить ее к генератору как метод с причудливым декоратором, если хотите.
честно говоря, это имеет смысл. Я знал, что нет способа определить длину генератора, но подумал, что, возможно, я пропустил способ узнать, будет ли он изначально генерировать что-либо вообще.
Дэн
1
Да, и для справки, я попытался реализовать свое собственное предложение «модного декоратора». ЖЕСТКИЙ. Видимо copy.deepcopy не работает на генераторах.
Дэвид Бергер,
51
Я не уверен, что могу согласиться с тем, что «простого пути быть не должно». В информатике существует множество абстракций, которые предназначены для вывода последовательности значений без сохранения последовательности в памяти, но которые позволяют программисту спрашивать, есть ли другое значение, не удаляя его из «очереди», если она есть. Существует такая вещь, как однократный просмотр вперед, не требующий «обратного просмотра». Это не значит, что дизайн итератора должен предоставлять такую функцию, но она, безусловно, полезна. Может быть, вы возражаете на том основании, что первое значение может измениться после взгляда?
LarsH
10
Я возражаю на том основании, что типичная реализация даже не вычисляет значение, пока оно не понадобится. Можно заставить интерфейс делать это, но это может быть неоптимальным для облегченных реализаций.
Дэвид Бергер
6
@ S.Lott вам не нужно генерировать всю последовательность, чтобы знать, пуста она или нет. Достаточно памяти для одного элемента - см. Мой ответ.
Марк Рэнсом
104
Предложение:
defpeek(iterable):try:
first = next(iterable)
except StopIteration:
returnNonereturn first, itertools.chain([first], iterable)
Применение:
res = peek(mysequence)
if res isNone:
# sequence is empty. Do stuff.else:
first, mysequence = res
# Do something with first, maybe?# Then iterate over the sequence:for element in mysequence:
# etc.
Я не совсем понимаю, что нужно возвращать первый элемент дважды return first, itertools.chain([first], rest).
njzk2
6
@ njzk2 Я собирался выполнить операцию "взглянуть" (отсюда и название функции). wiki «peek - это операция, которая возвращает значение вершины коллекции без удаления значения из данных»
Джон Фухи
Это не сработает, если генератор предназначен для выдачи None. def gen(): for pony in range(4): yield None if pony == 2 else pony
Пол
4
@Paul Внимательно посмотрите на возвращаемые значения. Если генератор завершен - то есть не возвращается None, а поднимает StopIteration- результатом функции является None. В противном случае это кортеж, которого нет None.
Иск Фонда Моники
Это очень помогло мне в моем текущем проекте. Я нашел аналогичный пример в коде для модуля стандартной библиотеки python mailbox.py. This method is for backward compatibility only. def next(self): """Return the next message in a one-time iteration.""" if not hasattr(self, '_onetime_keys'): self._onetime_keys = self.iterkeys() while True: try: return self[next(self._onetime_keys)] except StopIteration: return None except KeyError: continue
peer
31
Простой способ - использовать необязательный параметр для next (), который используется, если генератор исчерпан (или пуст). Например:
iterable = some_generator()
_exhausted = object()
if next(iterable, _exhausted) == _exhausted:
print('generator is empty')
Изменить: исправлена проблема, указанная в комментарии mehtunguh.
Нет. Это неверно для любого генератора, у которого первое полученное значение неверно.
mehtunguh 05
7
Используйте object()вместо , classчтобы сделать это одна строка короче: _exhausted = object(); if next(iterable, _exhausted) is _exhausted:
Messa
Почему предметы и все такое? Просто: if next(itreable,-1) == -1 тогда ген пустой!
Апостолос
@Apostolos Потому что next(iter([-1, -2, -3]), -1) == -1есть True. Другими словами, любая итерация с первым элементом, равным, -1будет отображаться как пустая с использованием вашего условия.
Jeyekomon
1
@Apostolos В простом случае да, это решение. Но это не удастся, если вы планируете создать общий инструмент для любой итерации без ограничений.
Джеэкомон,
16
next(generator, None) is not None
Или замените, Noneно какое бы значение вы ни знали, его нет в вашем генераторе.
Изменить : Да, это пропустит 1 элемент в генераторе. Однако часто я проверяю, пуст ли генератор, только в целях проверки, а затем не использую его. Или иначе я делаю что-то вроде:
deffoo(self):if next(self.my_generator(), None) isNone:
raise Exception("Not initiated")
for x in self.my_generator():
...
То есть это работает, если ваш генератор исходит из функции , например generator().
Почему это не лучший ответ? Если генератор вернется None?
Sait
8
Вероятно, потому что это заставляет вас фактически потреблять генератор, а не просто проверять, пуст ли он.
bfontaine
5
Это плохо, потому что в тот момент, когда вы позвоните next (generator, None), вы пропустите 1 элемент, если он доступен
Натан До
Правильно, вы пропустите 1-й элемент вашего поколения, а также вы собираетесь потреблять свое поколение, а не тестировать его, если он пуст.
AJ,
13
Лучше всего, ИМХО, избежать специальной проверки. В большинстве случаев использование генератора - это проверка:
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, но это немного ненадежно. Вместо этого просто установите флаг внутри блока и проверьте его потом:
ifnot thing_generated:
print"Avast, ye scurvy dog!"
Это решение будет пытаться использовать весь генератор, что сделает его непригодным для бесконечных генераторов.
Виктор Стискала 02
@ ViktorStískala: Я не понимаю твоей точки зрения. Было бы глупо проверять, дает ли бесконечный генератор какие-либо результаты.
vezult 07
Я хотел указать, что ваше решение может содержать перерыв в цикле for, потому что вы не обрабатываете другие результаты, и их создание бесполезно. range(10000000)является конечным генератором (Python 3), но вам не нужно просматривать все элементы, чтобы узнать, генерирует ли он что-либо.
Виктор Стискала
1
@ ViktorStískala: Понятно. Однако я хочу сказать следующее: как правило, вы действительно хотите работать с выходом генератора. В моем примере, если ничего не сгенерировано, теперь вы это знаете. В противном случае вы работаете с сгенерированным выводом, как и предполагалось - «Использование генератора - это проверка». Нет необходимости в специальных тестах или бессмысленном потреблении мощности генератора. Я отредактировал свой ответ, чтобы прояснить это.
vezult
9
Мне не нравится предлагать второе решение, особенно такое, которое я бы сам не использовал, но, если вам абсолютно необходимо это сделать и не использовать генератор, как в других ответах:
defdo_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 isnot empty_marker:
do_something_with_item(first_item)
for item in my_generator:
do_something_with_item(item)
Сейчас мне очень не нравится это решение, потому что я считаю, что генераторы должны использоваться не так.
Я понимаю, что этому посту на данный момент 5 лет, но я нашел его, когда искал идиоматический способ сделать это, и не видел опубликованного своего решения. Итак для потомков:
import itertools
defget_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.
Это вызовет genгенератор только один раз для каждого элемента, поэтому побочные эффекты не являются большой проблемой. Но он будет хранить копию всего, что было получено из генератора через b, но не через a, поэтому последствия для памяти аналогичны простому запуску list(gen)и проверке этого.
Маттиас Фрипп
Здесь есть две проблемы. 1. Этот инструмент itertool может потребовать значительного объема вспомогательной памяти (в зависимости от того, сколько временных данных необходимо сохранить). Как правило, если один итератор использует большую часть или все данные до запуска другого итератора, быстрее использовать list () вместо tee (). 2. итераторы tee не являются потокобезопасными. Ошибка RuntimeError может возникнуть при одновременном использовании итераторов, возвращаемых одним и тем же вызовом tee (), даже если исходная итерация является потокобезопасной.
AJ,
4
Все, что вам нужно сделать, чтобы увидеть, пуст ли генератор, - это попытаться получить следующий результат. Конечно, если вы не готовы использовать этот результат, вам нужно сохранить его, чтобы вернуть его позже.
Вот класс-оболочка, который можно добавить к существующему итератору для добавления __nonzero__теста, чтобы вы могли увидеть, пуст ли генератор, с помощью простого if. Возможно, его тоже можно превратить в декоратора.
Это движется в правильном направлении. Его следует изменить, чтобы можно было заглядывать вперед как можно дальше и сохранять столько результатов, сколько необходимо. В идеале это позволило бы помещать произвольные элементы в начало потока. Pushable-iterator - очень полезная абстракция, которую я часто использую.
sfkleach
@sfkleach Я не вижу необходимости усложнять это для многократного просмотра вперед, это довольно полезно как есть и отвечает на вопрос. Несмотря на то, что это старый вопрос, он все еще время от времени просматривается, поэтому, если вы хотите оставить свой собственный ответ, кто-то может счесть его полезным.
Марк Рэнсом
Марк совершенно прав в том, что его решение отвечает на вопрос, который является ключевым. Я должен был выразить это лучше. Я имел в виду, что толкаемые итераторы с неограниченным откатом - это идиома, которую я нашел чрезвычайно полезной, и реализация, возможно, даже проще. Как было предложено, я опубликую код варианта.
sfkleach
4
Просто заглянул в эту ветку и понял, что не хватает очень простого и легко читаемого ответа:
defis_empty(generator):for item in generator:
returnFalsereturnTrue
Если мы не предполагаем потреблять какой-либо элемент, нам нужно повторно ввести первый элемент в генератор:
defis_empty_no_side_effects(generator):try:
item = next(generator)
defmy_generator():yield item
yieldfrom generator
return my_generator(), Falseexcept 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]
По подсказке Марка Рэнсома, вот класс, который вы можете использовать для обертывания любого итератора, чтобы вы могли заглядывать вперед, возвращать значения в поток и проверять пустоту. Это простая идея с простой реализацией, которую я считал очень удобной в прошлом.
Извините за очевидный подход, но лучше всего было бы:
for item in my_generator:
print item
Теперь вы обнаружили, что генератор пуст, пока вы его используете. Конечно, элемент никогда не будет отображаться, если генератор пуст.
Это может не совсем соответствовать вашему коду, но идиома генератора предназначена для итерации, поэтому, возможно, вы можете немного изменить свой подход или вообще не использовать генераторы.
Или ... спрашивающий может подсказать, зачем пытаться обнаружить пустой генератор?
S.Lott
вы имели в виду "ничего не будет отображаться, поскольку генератор пуст"?
SilentGhost,
S.Lott. Согласен. Я не понимаю почему. Но я думаю, даже если бы была причина, лучше бы вместо этого использовать каждый элемент.
Али Афшар,
2
Это не сообщает программе, был ли генератор пуст.
Итан Фурман
1
>>> 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 [])
>>> ifnot list(gen):
print('empty generator')
Что на самом деле потребляет весь генератор. К сожалению, из вопроса неясно, желательно это или нежелательно.
S.Lott
как и любой другой способ "прикоснуться" к генератору, я полагаю.
SilentGhost,
Я понимаю, что это устарело, но использование list () не может быть лучшим способом, если сгенерированный список не пустой, а на самом деле большой, то это излишне расточительно
Chris_Rands
1
Если вам нужно знать, прежде чем использовать генератор, то нет, простого способа не существует. Если вы не можете ждать , пока после того, как вы использовали генератор, есть простой способ:
was_empty = Truefor some_item in some_generator:
was_empty = False
do_something_with(some_item)
if was_empty:
handle_already_empty_generator_case()
Просто оберните генератор с помощью itertools.chain , поместите что-то, что будет представлять конец итерации как второй итерируемый объект, а затем просто проверьте это.
Используйте eog = object()вместо того, чтобы предполагать, что float('-inf')этого никогда не произойдет в итерации.
bfontaine 09
@bfontaine Хорошая идея
smac89 09
1
В моем случае мне нужно было узнать, был ли заполнен набор генераторов, прежде чем я передал его функции, которая объединила элементы, т zip(...). Е .. Решение похоже, но достаточно отличается от принятого ответа:
deffilter_empty(iterables):for iterable in iterables:
itr_has_items, iterable = has_items(iterable)
if itr_has_items:
yield iterable
defmerge_iterables(iterables):
populated_iterables = filter_empty(iterables)
for items in zip(*populated_iterables):
# Use items for each "slice"
Моя конкретная проблема заключается в том, что итерируемые объекты либо пусты, либо имеют точно такое же количество записей.
Я обнаружил, что только это решение работает и для пустых итераций.
defis_generator_empty(generator):
a, b = itertools.tee(generator)
try:
next(a)
except StopIteration:
returnTrue, b
returnFalse, b
is_empty, generator = is_generator_empty(generator)
Или, если вы не хотите использовать исключение для этого, попробуйте использовать
defis_generator_empty(generator):
a, b = itertools.tee(generator)
for item in a:
returnFalse, b
returnTrue, b
is_empty, generator = is_generator_empty(generator)
В отмеченном решении вы не можете использовать его для пустых генераторов, таких как
Но чем это полезно, если вы действительно хотите поработать с элементами генератора? Просто вставка этого фрагмента перед основным кодом выглядит очень грязно WA
The Godfather
Вы бы заменили оператор break своим кодом.
Пауло Алвес,
Это явно не работает, если генератор производит более одного предмета.
Крестный отец
0
Вот мой простой подход, который я использую, чтобы продолжать возвращать итератор при проверке, получилось ли что-то, я просто проверяю, выполняется ли цикл:
n = 0for key, value in iterator:
n+=1yield key, value
if n == 0:
print ("nothing found in iterator)
break
Вот простой декоратор, который обертывает генератор, поэтому он возвращает None, если он пуст. Это может быть полезно, если вашему коду нужно знать, будет ли генератор что-либо производить, прежде чем его выполнять в цикле.
defgenerator_or_none(func):"""Wrap a generator function, returning None if it's empty. """definner(*args, **kwargs):# peek at the first item; return None if it doesn't existtry:
next(func(*args, **kwargs))
except StopIteration:
returnNone# return original generator otherwise first item will be missingreturn func(*args, **kwargs)
return inner
Применение:
import random
@generator_or_nonedefrandom_length_generator():for i in range(random.randint(0, 10)):
yield i
gen = random_length_generator()
if gen isNone:
print('Generator is empty')
Один из примеров, где это полезно, - это шаблонный код, например jinja2.
{% if content_generator %}
<section>
<h4>Section title</h4>
{% for item in content_generator %}
{{ item }}
{% endfor %
</section>
{% endif %}
При этом функция генератора вызывается дважды, поэтому затраты на запуск генератора увеличиваются дважды. Это может быть существенным, если, например, функцией генератора является запрос к базе данных.
Ян Голдби
0
используя islice, вам нужно только проверить до первой итерации, чтобы узнать, пуста ли она.
from itertools import islice
def isempty (iterable):
return list (islice (iterable, 1)) == []
Мы не можем использовать any () для генератора всего. Просто пытался использовать его с генератором, который содержит несколько фреймов данных. Я получил это сообщение «Истинное значение DataFrame неоднозначно». на любом (my_generator_of_df)
пробитайл 08
any(generator)работает, когда вы знаете, что генератор будет генерировать значения, которые могут быть преобразованы в bool- основные типы данных (например, int, string) работают. any(generator)будет иметь значение False, когда генератор пуст, или когда генератор имеет только ложные значения - например, если генератор собирается генерировать 0, '' (пустая строка) и False, тогда он все равно будет False. Это может быть, а может и не быть предполагаемым поведением, если вы об этом знаете :)
Даниэль
anyпотреблял бы все предметы от генератора до (включительно) первого правдивого предмета, в дополнение к проблемам, упомянутым выше
Адам
-2
Я решил это с помощью функции суммы. См. Ниже пример, который я использовал с glob.iglob (который возвращает генератор).
defisEmpty():
files = glob.iglob(search)
if sum(1for _ in files):
returnTruereturnFalse
* Это, вероятно, не будет работать для ОГРОМНЫХ генераторов, но должно хорошо работать для небольших списков
[]
удобно считать ложным, так что вы можете выполнить его проверку if и выполнить специальное поведение для чего-то или ничего. Генераторы верны, даже если они не дают никаких элементов.glob.iglob("filepattern")
предоставленный пользователем шаблон подстановки и хочу предупредить пользователя, если шаблон не соответствует ни одному файлу. Конечно, я могу обойти это разными способами, но полезно иметь возможность чисто проверить, оказался ли итератор пустым или нет.Ответы:
Простой ответ на ваш вопрос: нет, простого пути нет. Есть много способов обхода.
На самом деле не должно быть простого способа из-за того, что такое генераторы: способа вывода последовательности значений без сохранения последовательности в памяти . Так что обратного обхода нет.
Вы можете написать функцию 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.
источник
return first, itertools.chain([first], rest)
.def gen(): for pony in range(4): yield None if pony == 2 else pony
None
, а поднимаетStopIteration
- результатом функции являетсяNone
. В противном случае это кортеж, которого нетNone
.This method is for backward compatibility only. def next(self): """Return the next message in a one-time iteration.""" if not hasattr(self, '_onetime_keys'): self._onetime_keys = self.iterkeys() while True: try: return self[next(self._onetime_keys)] except StopIteration: return None except KeyError: continue
Простой способ - использовать необязательный параметр для next (), который используется, если генератор исчерпан (или пуст). Например:
iterable = some_generator() _exhausted = object() if next(iterable, _exhausted) == _exhausted: print('generator is empty')
Изменить: исправлена проблема, указанная в комментарии mehtunguh.
источник
object()
вместо ,class
чтобы сделать это одна строка короче:_exhausted = object()
;if next(iterable, _exhausted) is _exhausted:
if next(itreable,-1) == -1
тогда ген пустой!next(iter([-1, -2, -3]), -1) == -1
естьTrue
. Другими словами, любая итерация с первым элементом, равным,-1
будет отображаться как пустая с использованием вашего условия.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()
.источник
None
?Лучше всего, ИМХО, избежать специальной проверки. В большинстве случаев использование генератора - это проверка:
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!"
источник
range(10000000)
является конечным генератором (Python 3), но вам не нужно просматривать все элементы, чтобы узнать, генерирует ли он что-либо.Мне не нравится предлагать второе решение, особенно такое, которое я бы сам не использовал, но, если вам абсолютно необходимо это сделать и не использовать генератор, как в других ответах:
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 лет, но я нашел его, когда искал идиоматический способ сделать это, и не видел опубликованного своего решения. Итак для потомков:
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.
источник
gen
генератор только один раз для каждого элемента, поэтому побочные эффекты не являются большой проблемой. Но он будет хранить копию всего, что было получено из генератора черезb
, но не черезa
, поэтому последствия для памяти аналогичны простому запускуlist(gen)
и проверке этого.Все, что вам нужно сделать, чтобы увидеть, пуст ли генератор, - это попытаться получить следующий результат. Конечно, если вы не готовы использовать этот результат, вам нужно сохранить его, чтобы вернуть его позже.
Вот класс-оболочка, который можно добавить к существующему итератору для добавления
__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'
Обратите внимание, что вы можете проверить наличие пустоты в любое время, а не только в начале итерации.
источник
Просто заглянул в эту ветку и понял, что не хватает очень простого и легко читаемого ответа:
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]
источник
По подсказке Марка Рэнсома, вот класс, который вы можете использовать для обертывания любого итератора, чтобы вы могли заглядывать вперед, возвращать значения в поток и проверять пустоту. Это простая идея с простой реализацией, которую я считал очень удобной в прошлом.
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)
источник
Извините за очевидный подход, но лучше всего было бы:
for item in my_generator: print item
Теперь вы обнаружили, что генератор пуст, пока вы его используете. Конечно, элемент никогда не будет отображаться, если генератор пуст.
Это может не совсем соответствовать вашему коду, но идиома генератора предназначена для итерации, поэтому, возможно, вы можете немного изменить свой подход или вообще не использовать генераторы.
источник
>>> 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')
источник
Если вам нужно знать, прежде чем использовать генератор, то нет, простого способа не существует. Если вы не можете ждать , пока после того, как вы использовали генератор, есть простой способ:
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()
источник
Просто оберните генератор с помощью 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
источник
eog = object()
вместо того, чтобы предполагать, чтоfloat('-inf')
этого никогда не произойдет в итерации.В моем случае мне нужно было узнать, был ли заполнен набор генераторов, прежде чем я передал его функции, которая объединила элементы, т
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"
Моя конкретная проблема заключается в том, что итерируемые объекты либо пусты, либо имеют точно такое же количество записей.
источник
Я обнаружил, что только это решение работает и для пустых итераций.
def is_generator_empty(generator): a, b = itertools.tee(generator) try: next(a) except StopIteration: return True, b return False, b is_empty, generator = is_generator_empty(generator)
Или, если вы не хотите использовать исключение для этого, попробуйте использовать
def is_generator_empty(generator): a, b = itertools.tee(generator) for item in a: return False, b return True, b is_empty, generator = is_generator_empty(generator)
В отмеченном решении вы не можете использовать его для пустых генераторов, таких как
def get_empty_generator(): while False: yield None generator = get_empty_generator()
источник
Это старый вопрос, на который дан ответ, но, поскольку никто не показал его раньше, вот он:
for _ in generator: break else: print('Empty')
Вы можете прочитать больше здесь
источник
Вот мой простой подход, который я использую, чтобы продолжать возвращать итератор при проверке, получилось ли что-то, я просто проверяю, выполняется ли цикл:
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 %}
источник
используя islice, вам нужно только проверить до первой итерации, чтобы узнать, пуста ли она.
источник
Используйте заглянуть функцию в 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
Итератор, возвращаемый этой функцией, будет эквивалентен исходному, переданному в качестве аргумента.
источник
Есть очень простое решение:
if next(generator,-1) == -1
тогда генератор пуст!источник
А как насчет использования any ()? Я использую его с генераторами, и он работает нормально. Вот парень, немного объясняющий это
источник
any(generator)
работает, когда вы знаете, что генератор будет генерировать значения, которые могут быть преобразованы вbool
- основные типы данных (например, int, string) работают.any(generator)
будет иметь значение False, когда генератор пуст, или когда генератор имеет только ложные значения - например, если генератор собирается генерировать 0, '' (пустая строка) и False, тогда он все равно будет False. Это может быть, а может и не быть предполагаемым поведением, если вы об этом знаете :)any
потреблял бы все предметы от генератора до (включительно) первого правдивого предмета, в дополнение к проблемам, упомянутым вышеЯ решил это с помощью функции суммы. См. Ниже пример, который я использовал с glob.iglob (который возвращает генератор).
def isEmpty(): files = glob.iglob(search) if sum(1 for _ in files): return True return False
* Это, вероятно, не будет работать для ОГРОМНЫХ генераторов, но должно хорошо работать для небольших списков
источник