我如何知道生成器从一开始是否为空?

问题:我如何知道生成器从一开始是否为空?

有没有测试,如果生成器组没有项目,如一个简单的方法peekhasNextisEmpty,类似的规定?

Is there a simple way of testing if the generator has no items, like peek, hasNext, isEmpty, something along those lines?


回答 0

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

实际上,不应该有一种简单的方法,因为生成器是什么:一种在不将序列保存在内存中的情况下输出值序列的方法。因此,没有向后遍历。

您可以编写has_next函数,甚至可以将其作为带有精美装饰器的方法添加到生成器上。

The simple answer to your question: no, there is no simple way. There are a whole lot of work-arounds.

There really shouldn’t be a simple way, because of what generators are: a way to output a sequence of values without holding the sequence in memory. So there’s no backward traversal.

You could write a has_next function or maybe even slap it on to a generator as a method with a fancy decorator if you wanted to.


回答 1

建议:

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.

Suggestion:

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

Usage:

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.

回答 2

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

iterable = some_generator()

_exhausted = object()

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

编辑:更正了mehtunguh注释中指出的问题。

A simple way is to use the optional parameter for next() which is used if the generator is exhausted (or empty). For example:

iterable = some_generator()

_exhausted = object()

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

Edit: Corrected the problem pointed out in mehtunguh’s comment.


回答 3

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

next(generator, None) is not None

Or replace None but whatever value you know it’s not in your generator.

Edit: Yes, this will skip 1 item in the generator. Often, however, I check whether a generator is empty only for validation purposes, then don’t really use it. Or otherwise I do something like:

def foo(self):
    if next(self.my_generator(), None) is None:
        raise Exception("Not initiated")

    for x in self.my_generator():
        ...

That is, this works if your generator comes from a function, as in generator().


回答 4

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

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

The best approach, IMHO, would be to avoid a special test. Most times, use of a generator is the test:

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)

If that’s not good enough, you can still perform an explicit test. At this point, thing will contain the last value generated. If nothing was generated, it will be undefined – unless you’ve already defined the variable. You could check the value of thing, but that’s a bit unreliable. Instead, just set a flag within the block and check it afterward:

if not thing_generated:
    print "Avast, ye scurvy dog!"

回答 5

我讨厌提供第二种解决方案,尤其是我自己不会使用的解决方案,但是,如果您绝对必须这样做并且不消耗生成器,那么在其他答案中:

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)

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

I hate to offer a second solution, especially one that I would not use myself, but, if you absolutely had to do this and to not consume the generator, as in other answers:

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)

Now I really don’t like this solution, because I believe that this is not how generators are to be used.


回答 6

我意识到该帖子目前已有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)

当然,正如我敢肯定的,很多评论员都会指出,这很hacky,并且仅在某些有限的情况下才起作用(例如,生成器是无副作用的)。YMMV。

I realize that this post is 5 years old at this point, but I found it while looking for an idiomatic way of doing this, and did not see my solution posted. So for posterity:

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)

Of course, as I’m sure many commentators will point out, this is hacky and only works at all in certain limited situations (where the generators are side-effect free, for example). YMMV.


回答 7

很抱歉使用明显的方法,但是最好的方法是:

for item in my_generator:
     print item

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

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

Sorry for the obvious approach, but the best way would be to do:

for item in my_generator:
     print item

Now you have detected that the generator is empty while you are using it. Of course, item will never be displayed if the generator is empty.

This may not exactly fit in with your code, but this is what the idiom of the generator is for: iterating, so perhaps you might change your approach slightly, or not use generators at all.


回答 8

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

这是一个包装器类,可以将其添加到现有迭代器中以添加__nonzero__测试,因此您可以使用simple来查看生成器是否为空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'

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

All you need to do to see if a generator is empty is to try to get the next result. Of course if you’re not ready to use that result then you have to store it to return it again later.

Here’s a wrapper class that can be added to an existing iterator to add an __nonzero__ test, so you can see if the generator is empty with a simple if. It can probably also be turned into a decorator.

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)

Here’s how you’d use it:

with open(filename, 'r') as f:
    f = GenWrapper(f)
    if f:
        print 'Not empty'
    else:
        print 'Empty'

Note that you can check for emptiness at any time, not just at the start of the iteration.


回答 9

在马克·兰瑟姆(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)

Prompted by Mark Ransom, here’s a class that you can use to wrap any iterator so that you can peek ahead, push values back onto the stream and check for empty. It’s a simple idea with a simple implementation that I’ve found very handy in the past.

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)

回答 10

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

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]

Just fell on this thread and realized that a very simple and easy to read answer was missing:

def is_empty(generator):
    for item in generator:
        return False
    return True

If we are not suppose to consume any item then we need to re-inject the first item into the generator:

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

Example:

>>> 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]

回答 11

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

At the end of generator StopIteration is raised, since in your case end is reached immediately, exception is raised. But normally you shouldn’t check for existence of next value.

another thing you can do is:

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

回答 12

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

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

If you need to know before you use the generator, then no, there is no simple way. If you can wait until after you have used the generator, there is a simple way:

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

回答 13

只需用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

Simply wrap the generator with itertools.chain, put something that will represent the end of the iterable as the second iterable, then simply check for that.

Ex:

import itertools

g = some_iterable
eog = object()
wrap_g = itertools.chain(g, [eog])

Now all that’s left is to check for that value we appended to the end of the iterable, when you read it then that will signify the end

for value in wrap_g:
    if value == eog: # DING DING! We just found the last element of the iterable
        pass # Do something

回答 14

在我的情况下,我需要先了解是否填充了许多生成器,然后再将其传递给一个函数,该函数合并了各个项,即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"

我的特定问题具有以下属性:可迭代项为空或具有完全相同的条目数。

In my case I needed to know if a host of generators was populated before I passed it on to a function, which merged the items, i.e., zip(...). The solution is similar, but different enough, from the accepted answer:

Definition:

def has_items(iterable):
    try:
        return True, itertools.chain([next(iterable)], iterable)
    except StopIteration:
        return False, []

Usage:

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"

My particular problem has the property that the iterables are either empty or has exactly the same number of entries.


回答 15

我发现只有这种解决方案也可以用于空迭代。

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

I found only this solution as working for empty iterations as well.

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)

Or if you do not want to use exception for this try to use

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)

In the marked solution you are not possible to use it for empty generators like

def get_empty_generator():
    while False:
        yield None 

generator = get_empty_generator()

回答 16

这是一个古老且已回答的问题,但正如以前没有人显示过的那样,它来了:

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

你可以在这里阅读更多

This is an old and answered question, but as no one has shown it before, here it goes:

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

You can read more here


回答 17

这是我使用的简单方法,用于在检查是否产生某些结果时继续返回迭代器,而只是检查循环是否运行:

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

Here is my simple approach that i use to keep on returning an iterator while checking if something was yielded I just check if the loop runs:

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

回答 18

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

Here’s a simple decorator which wraps the generator, so it returns None if empty. This can be useful if your code needs to know whether the generator will produce anything before looping through it.

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

Usage:

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

One example where this is useful is in templating code – i.e. jinja2

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

回答 19

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

从itertools导入islice

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

using islice you need only check up to the first iteration to discover if it is empty.

from itertools import islice

def isempty(iterable):
    return list(islice(iterable,1)) == []


回答 20

怎么样使用any()?我将其与生成器配合使用,并且工作正常。这里有人解释一下

What about using any()? I use it with generators and it’s working fine. Here there is guy explaining a little about this


回答 21

在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

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

Use the peek function in 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

The iterator returned by this function will be equivalent to the original one passed in as an argument.


回答 22

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

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

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

I solved it by using the sum function. See below for an example I used with glob.iglob (which returns a generator).

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

*This will probably not work for HUGE generators but should perform nicely for smaller lists