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

2025-02-07 08:44:00
admin
原创
81
摘要:问题描述:是否有一种简单的方法来测试生成器是否没有任何项目,例如peek,,,hasNext诸如此类isEmpty的东西?解决方案 1:建议:def peek(iterable): try: first = next(iterable) except StopIteration...

问题描述:

是否有一种简单的方法来测试生成器是否没有任何项目,例如peek,,,hasNext诸如此类isEmpty的东西?


解决方案 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.

解决方案 2:

对你的问题的简单回答是:没有,没有简单的方法。有很多解决方法。

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

如果您愿意的话,您可以编写一个 has_next 函数,或者甚至可以将其作为带有精美装饰器的方法附加到生成器上。

解决方案 3:

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

_exhausted  = object()

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

解决方案 4:

快速解决方案:

next(my_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():
        ...

也就是说,如果您的生成器来自函数,则此方法有效,如my_generator()

解决方案 5:

在我看来,最好的方法是避免进行特殊测试。大多数情况下,使用生成器进行测试:

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

解决方案 6:

刚刚浏览到这个帖子,发现缺少一个非常简单易读的答案:

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]

解决方案 7:

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

解决方案 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)

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

解决方案 9:

要判断生成器是否为空,只需尝试获取下一个结果即可。当然,如果您不准备使用该结果,则必须将其存储起来以便稍后再次返回。

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

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

解决方案 10:

抱歉,方法太明显了,但最好的方法是:

for item in my_generator:
     print item

现在你在使用生成器时检测到它是空的。当然,如果生成器是空的,item 永远不会显示。

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

解决方案 11:

我意识到这篇文章已经有 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。

解决方案 12:

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

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

解决方案 13:

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

解决方案 14:

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

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

解决方案 15:

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

解决方案 16:

在我的例子中,我需要知道在将生成器主机传递给合并项的函数之前是否填充了大量生成器,即。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"

我的特定问题具有这样的属性:可迭代对象要么为空,要么具有完全相同数量的条目。

解决方案 17:

使用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:

为了尝试提供我的“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

这可能不是最好的方法,主要是因为它消耗了发电机,但它对我来说是有效的。

解决方案 19:

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

is_empty = True

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

if is_empty:
    print('Generator is empty')

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

解决方案 20:

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

peekablefrommore-itertools允许通过检查其真值来检查它是否已用尽。使用一个空迭代器和一个非空迭代器的演示:

from more_itertools import peekable

for source in '', 'foobar':
    it = iter(source)
    
    if it := peekable(it):
        print('values:', *it)
    else:
        print('empty')

输出:

empty
values: f o o b a r

解决方案 22:

这是一个老问题,但之前没有人回答过,因此这里是:

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

您可以在此处阅读更多内容

解决方案 23:

现有的答案使用自定义解决方案来解决此问题,但可以通过获取现成的解决方案more_itertools.peekable

这使得用户可以保留迭代器的内存效率,同时:

  • 能够检查下一个可用元素但不使用它

  • 检查生成器是否为空

    from more_itertools import peekable

    def gen_fun(n: int):
        for i in range(n):
            yield i

    generator_object = gen_fun(5)
    peekable_generator_object = peekable(generator_object)
    if not peekable_generator_object:
        print("Empty Generator")

解决方案 24:

有一个非常简单的解决方案:if next(generator,-1) == -1那么生成器就是空的!

解决方案 25:

我使用 sum 函数解决了这个问题。下面是我使用 glob.iglob(返回一个生成器)的示例。

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

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

解决方案 26:

bool(generator) 将返回正确的结果

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1579  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1355  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   8  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   9  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用