如何从一开始就知道发电机是否为空?

时间:2009-03-19 09:51:24

标签: python generator

是否有一种简单的方法可以测试生成器是否没有项目,例如peek,hasNext,isEmpty,这些是什么?

25 个答案:

答案 0 :(得分:81)

建议:

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.

答案 1 :(得分:46)

你问题的简单答案:不,没有简单的方法。有很多解决方法。

确实不应该有一个简单的方法,因为生成器是什么:一种输出值序列而不将序列保存在内存中的方法。所以没有向后遍历。

如果你愿意的话,你可以编写一个has_next函数,甚至可以将它作为一个带花式装饰器的方法打到生成器上。

答案 2 :(得分:23)

一种简单的方法是使用next()的可选参数,如果生成器耗尽(或为空),则使用该参数。例如:

iterable = some_generator()

_exhausted = object()

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

编辑:更正了mehtunguh评论中指出的问题。

答案 3 :(得分:9)

None

或者替换def foo(self): if next(self.my_generator(), None) is None: raise Exception("Not initiated") for x in self.my_generator(): ... ,但无论您知道它在发电机中不是的价值。

修改:是的,这将跳过生成器中的1个项目。但是,我经常检查生成器是否为空以用于验证目的,然后不要真正使用它。或者我做的事情如下:

generator()

也就是说,如果你的生成器来自函数,就像{{1}}一样。

答案 4 :(得分:9)

最好的方法,恕我直言,将避免一个特殊的测试。大多数情况下,使用生成器 测试:

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!"

答案 5 :(得分:8)

我讨厌提供第二种解决方案,尤其是我不会自己使用的解决方案,但是,如果您绝对 要执行此操作而不使用生成器,就像在其他答案中一样:

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)

现在我真的不喜欢这个解决方案,因为我认为这不是生成器的使用方式。

答案 6 :(得分:3)

对于明显的方法感到抱歉,但最好的办法是:

for item in my_generator:
     print item

现在您在使用它时检测到生成器为空。当然,如果生成器为空,则永远不会显示项目。

这可能与你的代码完全不符,但这就是生成器的惯用语:迭代,所以也许你可能会略微改变你的方法,或者根本不使用生成器。

答案 7 :(得分:3)

要查看生成器是否为空,您需要做的就是尝试获取下一个结果。当然,如果您没有准备好来使用该结果,那么您必须将其存储以便稍后再次返回。

这是一个包装类,可以添加到现有的迭代器中以添加__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'

请注意,您可以随时检查空虚,而不仅仅是在迭代开始时。

答案 8 :(得分:3)

我意识到这篇文章目前还有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 :(得分:2)

这是一个古老且已回答的问题,但由于以前没有人显示过,所以它就来了:

for _ in generator:
    break
else:
    print('Empty')

You can read more here

答案 10 :(得分:1)

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

引发生成器StopIteration的末尾,因为在您的情况下立即到达end,会引发异常。 但通常您不应检查是否存在下一个值。

你可以做的另一件事是:

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

答案 11 :(得分:1)

我只发现此解决方案也适用于空迭代。

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)

marked solution中,您无法将其用于空生成器,例如

def get_empty_generator():
    while False:
        yield None 

generator = get_empty_generator()

答案 12 :(得分:1)

就在这个线程上,并且意识到缺少一个非常简单易读的答案:

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]

答案 13 :(得分:1)

在我的情况下,我需要知道在将其传递给函数之前是否已填充了大量生成器,该函数合并了项目,即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"

我的特殊问题是迭代物为空或具有完全相同数量的条目的属性。

答案 14 :(得分:0)

有一个非常简单的解决方案:if next(generator,-1) == -1然后生成器为空!

答案 15 :(得分:0)

为了帮助我的“2美分”,我将描述我的经历:

我有一个生成器,我需要使用 itertools.islice 将其切成小生成器。然后检查我的子生成器是否为空,我只是将它们转换/使用到一个小列表中,然后检查列表是否为空。

例如:

from itertools import islice

def generator(max_yield=10):
    a = 0

    while True:
        a += 1

        if a > max_yield:
            raise StopIteration()

        yield a

tg = generator()

label = 1

while True:
    itg = list(islice(tg, 3))

    if not itg:  # <-- I check if the list is empty or not
        break

    for i in itg:
        print(f'#{label} - {i}')

    label += 1

输出:

#1 - 1
#1 - 2
#1 - 3
#2 - 4
#2 - 5
#2 - 6
#3 - 7
#3 - 8
#3 - 9
#4 - 10

也许这不是最好的方法,主要是因为它消耗了生成器,但它对我有用。

答案 16 :(得分:0)

这是一个由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)

答案 17 :(得分:0)

使用cytoolz中的peek功能。

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

此函数返回的迭代器将等同于作为参数传入的原始迭代器。

答案 18 :(得分:0)

使用any()怎么样?我用它与发电机一起工作正常。 Here有人解释了一下这个

答案 19 :(得分:0)

使用islice,你只需要检查第一次迭代,发现它是否为空。

  

来自itertools import islice

     

def isempty(iterable):
  返回列表(islice(iterable,1))== []

答案 20 :(得分:0)

这是一个包装生成器的简单装饰器,因此如果为空则返回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 %}

答案 21 :(得分:0)

简单地用itertools.chain包装生成器,将代表迭代结尾的东西放到第二个迭代中,然后直接检查它。

例如:

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

答案 22 :(得分:0)

这是我用来继续返回迭代器的简单方法,同时检查是否产生了某些东西 我只是检查循环是否运行:

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

答案 23 :(得分:0)

如果您需要在之前知道,那么使用生成器,那么不,没有简单的方法。如果您可以等到之后使用了生成器,那么有一种简单的方法:

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

答案 24 :(得分:-2)

我使用sum函数解决了它。请参阅下面我与glob.iglob(返回生成器)一起使用的示例。

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

*这可能不适用于HUGE生成器,但对于较小的列表应该可以很好地执行