【问题标题】:Recursive generators递归生成器
【发布时间】:2012-11-26 23:48:14
【问题描述】:

有时我发现自己在 Python 中编写递归生成器。这是recent example

def comb(input, lst = [], lset = set()):
   if lst:
      yield lst
   for i, el in enumerate(input):
      if lset.isdisjoint(el):
         for out in comb(input[i+1:], lst + [el], lset | set(el)):
            yield out

for c in comb([[1, 2, 3], [3, 6, 8], [4, 9], [6, 11]]):
   print c

算法的细节并不重要。我将它作为一个完整的真实世界插图包含在内,以便为这个问题提供一些背景信息。

我的问题是关于以下构造:

   for out in comb(...):
      yield out

这里,comb() 是生成器的递归实例化。

每次我必须拼出for: yield 循环时,都会让我畏缩。这真的是 在 Python 中编写递归生成器的方式,还是有更好的(更惯用的、性能更高的等)替代方案?

【问题讨论】:

  • Python 3.3 具有 yield from <generator> 构造,但尚未向后移植。
  • 在 Py3.3 上使用新的 yield from 语法。它是为这些事情而设计的
  • 天哪!可惜我还在 Python 2.x 上。 :(
  • @Wessie:+1,但是……“还没有被反向移植”?您预计何时将其向后移植,以及移植到什么版本?没有将来的版本可以将其反向移植到。
  • @abarnert:您确实是对的,它不会被反向移植到早期版本中的语言结构中。我记得Pure Python yield from.

标签: python recursion generator


【解决方案1】:

每次我必须拼出 for:yield 循环时,我都会感到畏缩。这真的是用 Python 编写递归生成器的方法吗,还是有更好的(更惯用的、更高性能的等)替代方案?

有一个更好的选择:

yield from comb(...)

这实际上与以下内容相同:

for out in comb(...):
    yield out

这需要 Python 3.3。如果您坚持使用 Python 2.x(或更早的 3.x),则必须坚持使用旧方式,因为 Python 2 的语法在 2.7 之后将永远不会再次更新(并且 3.0 到 3.2 显然同样被冻结)。

首先,看看 Wessie 在 cmets 中提到的pure Python yield from。此版本仅适用于单一级别的“yield from”,但底部有一个链接,指向更灵活和优化(但更难理解)的版本。它似乎并没有真正起作用(我在_stack 上得到了一个NameError,但它看起来应该很容易修复。如果是这样,并且如果可以在最外面的生成器上放置一个@supergenerator 装饰器,如果性能可以接受,这就是你的答案。

如果没有,您可以采取各种技巧来在一个地方而不是在每个级别处理多个级别的 yield-looping。然而,它们都不会让你降到 0 级——而且真的,它们很少值得去做。例如:

一旦您考虑的是序列而不是生成器函数,很明显我们要做的就是展平序列。无论您是要尝试展平 N 个级别,展平直到达到不可迭代,展平直到满足其他一些可预测的级别,等等,都有一个简单的算法;你只需要选择正确的。但它会让你的代码更惯用、更易读、更高效等吗?很少。让我们举一个超级简单的案例。

def flatten(seq, levels=1):
    for level in range(levels):
        seq = itertools.chain.from_iterable(seq)
    return seq

所以:

def a():
    yield 1
    yield 2
    yield 3
def b():
    yield a()
def c():
    yield b()
def d():
    yield c()

for i in flatten(d(), 3):
    print i

好处是我只需要在调用站点的一个地方处理嵌套,而不是在沿途的每个生成器的 3 个地方。代价是读者不太清楚发生了什么,而且更容易出错。 (好吧,在这种情况下不是那么多......但想象一下扁平化直到lambda x: isinstance(list),测试它,释放它,然后有人在tuple上调用comb......)治愈比疾病更糟糕,这就是为什么我称之为诡计。

除非展平确实是算法的自然部分,或者中间的某些步骤是您不能或不想接触的代码,或者以这种方式构建事物是一个有用的说明或提醒什么东西,或者……

只是为了好玩,我写了一个全唱全跳的 flatten-any-way-you-want 函数,并将它作为一个补丁提交给 Erik Rose 的漂亮 more-itertools 库。即使他不接受,你也可以在my fork 中找到它——它被称为collapse,它是文件中的最后一个函数。

【讨论】:

    猜你喜欢
    • 2019-08-25
    • 2021-05-22
    • 2020-08-04
    • 2016-05-03
    • 1970-01-01
    • 1970-01-01
    • 2020-08-16
    • 2020-02-09
    • 2013-09-16
    相关资源
    最近更新 更多