是否有一种简单的方法来测试生成器是否没有项目,比如peek, hasNext, isEmpty之类的?


当前回答

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

其他回答

在遍历生成器之前检查生成器符合LBYL编码风格。另一种方法(EAFP)是遍历它,然后检查它是否为空。

is_empty = True

for item in generator:
    is_empty = False
    do_something(item)

if is_empty:
    print('Generator is empty')

这种方法也可以很好地处理无限生成器。

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

我讨厌提供第二个解决方案,特别是一个我自己不会使用的解决方案,但是,如果你绝对必须这样做,并且不消耗生成器,就像在其他答案中一样:

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)

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

下面是一个包装生成器的简单装饰器,因此如果为空,它将返回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')

一个有用的例子是在模板代码中—即jinj2

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

我用求和函数解出来了。请参阅下面我使用glob的示例。Iglob(返回一个生成器)。

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

*这可能不适用于巨大的生成器,但对于较小的列表应该表现得很好