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

Dan*_*Dan 127 python generator

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

Joh*_*uhy 88

建议:

def peek(iterable):
    try:
        first = next(iterable)
    except StopIteration:
        return None
    return first, itertools.chain([first], iterable)
Run Code Online (Sandbox Code Playgroud)

用法:

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.
Run Code Online (Sandbox Code Playgroud)

  • @ njzk2我正在进行"偷看"操作(因此是函数名称).[wiki](http://en.wikipedia.org/wiki/Peek_%28data_type_operation%29)"peek是一个返回集合顶部值而不从数据中删除值的操作" (4认同)
  • 我不太明白在`return first,itertools.chain([first],rest)`中两次返回第一个元素. (2认同)
  • 这应该标记为答案. (2认同)
  • 如果生成器被设计为产生 None,则这将不起作用。`def gen(): 对于范围 (4) 中的小马:如果小马 == 2,则不产生任何小马` (2认同)
  • @Paul仔细看看返回值.如果生成器完成 - 即,不返回`None`,而是提高`StopIteration` - 函数的结果是'None`.否则,它是一个元组,不是"无". (2认同)
  • 大量的“peek”调用不会创建一个永无止境的“itertools.chain”对象链,其中包含对其他“itertools.chain”对象的引用吗? (2认同)

Dav*_*ger 49

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

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

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

  • 我不确定我是否同意"不应该有一个简单的方法".计算机科学中有很多抽象设计用于输出一系列值而不将序列保存在内存中,但允许程序员在没有将其从"队列"中删除的情况下询问是否存在其他值.不需要"向后遍历"就有这样的事情.这并不是说迭代器设计必须提供这样的功能,但它肯定是有用的.也许你反对的是第一个值可能会在偷看之后发生变化? (36认同)
  • 我反对的理由是,在需要之前,典型的实现甚至不会计算值.可以强制接口执行此操作,但这对于轻量级实现可能不是最佳的. (7认同)
  • @ S.Lott你不需要生成整个序列来知道序列是否为空.一个元素的存储空间就足够了 - 请参阅我的回答. (4认同)
  • 足够公平,这是有道理的。我知道无法找到生成器的长度,但是以为我可能会错过找到它最初是否会生成任何东西的方法。 (2认同)
  • 描述太复杂,而且没有明显的解决方案,无法获得 55 票!!! (2认同)

raz*_*zz0 26

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

iterable = some_generator()

_exhausted = object()

if next(iterable, _exhausted) == _exhausted:
    print('generator is empty')
Run Code Online (Sandbox Code Playgroud)

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

  • @Apostolos因为`next(iter([-1, -2, -3]), -1) == -1`是`True`。换句话说,使用您的条件,第一个元素等于“-1”的任何可迭代对象都将显示为空。 (9认同)
  • 使用`object()`而不是`class`使它缩短一行:`_exhausted = object()`; `if next(iterable,_exhausted)是_exhausted:` (7认同)
  • @Apostolos `object()` 是一个非凡的值,不会包含在生成器中。 (3认同)
  • 注意;这仍然是一个“查看”功能,将从生成器中取出一个元素。 (3认同)
  • @Apostolos 在简单的情况下,是的,这就是解决方案。但是,如果您计划为任何可迭代对象创建一个不受限制的通用工具,那么它就会失败。 (2认同)

jua*_*cks 10

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():
        ...
Run Code Online (Sandbox Code Playgroud)

也就是说,如果您的生成器来自某个函数,则可以使用此函数generator().

  • 可能是因为这会迫使你实际使用生成器,而不仅仅是测试它是否为空. (6认同)
  • 为什么这不是最好的答案?如果发电机返回"无"? (4认同)
  • 这很糟糕,因为你下次呼叫的那一刻(发电机,无)你将跳过1项(如果有的话) (3认同)
  • 是的,你将错过你的 gen 的第一个元素,并且你将消耗你的 gen 而不是测试它是否为空。 (2认同)

vez*_*ult 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)
Run Code Online (Sandbox Code Playgroud)

如果这还不够好,您仍然可以执行明确的测试.此时,thing将包含生成的最后一个值.如果没有生成任何内容,它将是未定义的 - 除非您已经定义了变量.你可以查看它的值thing,但这有点不可靠.相反,只需在块中设置一个标志,然后检查它:

if not thing_generated:
    print "Avast, ye scurvy dog!"
Run Code Online (Sandbox Code Playgroud)

  • 该解决方案将尝试消耗整个发电机,从而使其无法用于无限发电机. (2认同)
  • @ViktorStískala:我不明白你的意思。测试无限生成器是否产生任何结果是愚蠢的。 (2认同)
  • @ViktorStískala:明白了。但是,我的观点是:通常,您实际上想要对生成器输出进行操作。在我的示例中,如果没有生成任何内容,您现在就知道了。否则,您按预期对生成的输出进行操作 - “使用生成器就是测试”。无需特殊测试,或无意义地消耗发电机输出。我已经编辑了我的答案以澄清这一点。 (2认同)

Ali*_*har 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)
Run Code Online (Sandbox Code Playgroud)

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


sfk*_*ach 6

在 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)
Run Code Online (Sandbox Code Playgroud)


小智 6

刚刚落入这个线程并意识到缺少一个非常简单易读的答案:

def is_empty(generator):
    for item in generator:
        return False
    return True
Run Code Online (Sandbox Code Playgroud)

如果我们不打算消耗任何项目,那么我们需要将第一个项目重新注入生成器:

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
Run Code Online (Sandbox Code Playgroud)

例子:

>>> 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]
Run Code Online (Sandbox Code Playgroud)


Ali*_*har 5

对于显而易见的方法感到抱歉,但最好的方法是:

for item in my_generator:
     print item
Run Code Online (Sandbox Code Playgroud)

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

这可能不完全适合您的代码,但这就是生成器的习惯用法:迭代,所以也许您可能会稍微改变您的方法,或者根本不使用生成器。

  • 这不会告诉程序生成器是否为空。 (4认同)

Mar*_*som 5

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

这是一个包装类,可以将其添加到现有迭代器中以添加__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)
Run Code Online (Sandbox Code Playgroud)

以下是您如何使用它:

with open(filename, 'r') as f:
    f = GenWrapper(f)
    if f:
        print 'Not empty'
    else:
        print 'Empty'
Run Code Online (Sandbox Code Playgroud)

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