【问题标题】:Continue to other generators once a generator has been exhausted in a list of generators?一旦发电机列表中的发电机用尽,是否继续使用其他发电机?
【发布时间】:2021-12-13 07:19:07
【问题描述】:

我在函数 alternate_all(*args) 中有一个生成器列表,它在列表中的每个生成器之间交替打印它们的第一项、第二项……等等,直到所有生成器都用完。

我的代码在生成器耗尽之前一直工作,一旦发生 StopIteration,它就会停止打印,当我希望它继续使用其余生成器并忽略耗尽的生成器时:

def alternate_all(*args):
    iter_list = []
    for iterable in args:
        iter_list.append(iter(iterable))
    try:
        while True:
            for iterable in iter_list:
                val = next(iter_list[0])
                iter_list.append(iter_list.pop(0))
                yield val
    except StopIteration:
        pass

            
if __name__ == '__main__':
    for i in alternate_all('abcde','fg','hijk'):
        print(i,end='')

我的输出是:

afhbgic

什么时候应该:

afhbgicjdke

我怎样才能让它忽略耗尽的发电机?我宁愿不使用 itertools 并保持相同的结构。

【问题讨论】:

  • 您有什么理由不想使用itertools.zip_longest
  • 如果迭代器已用尽,您将在 next(iter_list[0]) 中收到 StopIteration 异常。你应该抓住它并处理它。
  • 我正在学习生成器,并试图在不使用 itertools 简化解决方案的情况下理解它们。这是作业要求
  • 对不起,我应该更具体一些。您应该在while 循环内处理它,以便它保持yielding。然后,您应该实现在所有迭代器都用尽时退出的逻辑。
  • @tdelaney 使用推导式当然更传统,尽管我个人更喜欢将map 与命名函数一起使用。老实说,我不明白 Guido 讨厌 map()

标签: python generator


【解决方案1】:

看Kaya的回答,好多了。

【讨论】:

  • 感谢您的帮助。我尝试了类似的方法,在我的 except 块中弹出第一个生成器,但它从未返回到 try 块。你知道为什么吗?我弹出第一个生成器,因为我假设当我使用iter_list.append(iter_list.pop(0)) 将前一个第一个索引生成器移动到列表的最后一个索引时,它会耗尽。编辑:啊,忽略此评论。我没有将 except 块放在 while 循环下。 @Selcuk 提到了这一点。道歉。
  • 在 cmets 中已经指出了问题。本质上,您是在 try 块内运行 while 循环。只要您的try 块捕获异常(即第一个生成器耗尽),except 块就会运行,这只是一个空操作。那时,您已经跳出了while 循环。
  • 对于''.join(alternating_iterators('a', 'bc', 'de')),这将给出'abdec',而我预计'abdce' 会是理想的结果。
  • @kaya3 是的,'de' 子字符串甚至不是交替的(如果可以的话),它是来自同一个可迭代源的两个连续值。对于名称以“交替”开头的函数来说似乎很糟糕。也许''.join(alternating_iterators('a', 'bb', 'cc')) 更清楚,他们的结果是'abccb' 而不是'abcbc'
  • 看起来并不“微不足道”,因为"".join(alternating_iterators("aaa", "bbbb", "cccc")) 仍然产生'abcabcabccb'
【解决方案2】:

itertools-recipe roundrobin 怎么样,直接来自 itertools 文档?不过,您最终还是会使用 itertools.cycleitertools.islice,但不确定这是否会破坏交易。

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    num_active = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while num_active:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            # Remove the iterator we just exhausted from the cycle.
            num_active -= 1
            nexts = cycle(islice(nexts, num_active))

【讨论】:

  • OP 明确声明他们希望避免使用itertools
【解决方案3】:

这行得通。我试图与您的原始代码的工作方式保持一致(尽管为了简单起见,我确实用列表理解替换了您的第一个循环)。

def alternate_all(*args):
    iter_list = [iter(arg) for arg in args]
    while iter_list:
        i = iter_list.pop(0)
        try:
            val = next(i)
        except StopIteration:
            pass
        else:
            yield val
            iter_list.append(i)

您的代码的主要问题是您的 try/except 在循环之外,这意味着第一个用尽的迭代器将从循环中退出。相反,您想在循环中捕获 StopIteration 以便您可以继续进行,并且循环应该继续进行,而 iter_list 中仍然有任何迭代器。

【讨论】:

  • @newbiecoder11 请接受这个答案而不是我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-12
  • 2016-10-20
  • 2017-07-21
  • 2014-01-28
  • 2015-07-08
  • 1970-01-01
相关资源
最近更新 更多