【问题标题】:Can generators be recursive?生成器可以递归吗?
【发布时间】:2016-11-10 07:02:38
【问题描述】:

我天真地尝试创建一个递归生成器。没用。这就是我所做的:

def recursive_generator(lis):
    yield lis[0]
    recursive_generator(lis[1:])

for k in recursive_generator([6,3,9,1]):
    print(k)

我得到的只是第一个项目6

有没有办法让这样的代码工作?本质上是将yield 命令以递归方案转移到上述级别?

【问题讨论】:

  • 当你再次调用它时,你并没有屈服。它达到第一个收益,没有看到另一个收益语句,然后退出。
  • 你要么需要yield from another_generator(),要么在while循环中显式地一个一个地生成每个元素。 another_generator() 在您的术语中是否“递归” - 这并不重要。

标签: python recursion generator


【解决方案1】:

您的递归调用只执行一次的原因是您实际上是在创建嵌套生成器。也就是说,每次递归调用函数 recursive_generator 时,都会在生成器中创建一个新生成器。

试试下面的,你会看到的。

def recursive_generator(lis):
    yield lis[0]
    yield recursive_generator(lis[1:])

for k in recursive_generator([6,3,9,1]):
    print(type(k))

与其他人提到的一样,一个简单的解决方案是使用yield from

【讨论】:

    【解决方案2】:

    递归生成器对于遍历非线性结构很有用。例如,让二叉树为 None 或值的元组,左树,右树。递归生成器是访问所有节点的最简单方法。示例:

    tree = (0, (1, None, (2, (3, None, None), (4, (5, None, None), None))),
            (6, None, (7, (8, (9, None, None), None), None)))
    
    def visit(tree):  # 
        if tree is not None:
            try:
                value, left, right = tree
            except ValueError:  # wrong number to unpack
                print("Bad tree:", tree)
            else:  # The following is one of 3 possible orders.
                yield from visit(left)
                yield value  # Put this first or last for different orders.
                yield from visit(right)
    
    print(list(visit(tree)))
    
    # prints nodes in the correct order for 'yield value' in the middle.
    # [1, 3, 2, 5, 4, 0, 6, 9, 8, 7]
    

    编辑:将if tree 替换为if tree is not None 以捕获其他错误值作为错误。

    编辑 2: 关于将递归调用放在 try: 子句中(@jpmc26 评论)。

    对于坏节点,上面的代码只记录 ValueError 并继续。例如,如果(9,None,None)(9,None) 替换,则输出为

    Bad tree: (9, None)
    [1, 3, 2, 5, 4, 0, 6, 8, 7]
    

    更典型的是在记录后重新加注,使输出成为

    Bad tree: (9, None)
    Traceback (most recent call last):
      File "F:\Python\a\tem4.py", line 16, in <module>
        print(list(visit(tree)))
      File "F:\Python\a\tem4.py", line 14, in visit
        yield from visit(right)
      File "F:\Python\a\tem4.py", line 14, in visit
        yield from visit(right)
      File "F:\Python\a\tem4.py", line 12, in visit
        yield from visit(left)
      File "F:\Python\a\tem4.py", line 12, in visit
        yield from visit(left)
      File "F:\Python\a\tem4.py", line 7, in visit
        value, left, right = tree
    ValueError: not enough values to unpack (expected 3, got 2)
    

    回溯给出了从根到坏节点的路径。可以包装原始的visit(tree) 调用以减少对路径的回溯:(根、右、右、左、左)。

    如果递归调用包含在 try: 子句中,则会在树的每个级别重新捕获、重新记录和重新引发错误。

    Bad tree: (9, None)
    Bad tree: (8, (9, None), None)
    Bad tree: (7, (8, (9, None), None), None)
    Bad tree: (6, None, (7, (8, (9, None), None), None))
    Bad tree: (0, (1, None, (2, (3, None, None), (4, (5, None, None), None))), (6, None, (7, (8, (9, None), None), None)))
    Traceback (most recent call last):
    ...  # same as before
    

    多个日志记录报告可能更多的是噪音而不是帮助。如果想要到坏节点的路径,最简单的方法是将每个递归调用包装在它自己的 try: 子句中,并在每个级别引发一个新的 ValueError,到目前为止构建的路径。

    结论:如果没有使用异常进行流控制(例如,可以使用 IndexError 进行),try: 语句的存在和位置取决于人们想要的错误报告。

    【讨论】:

    • 我认为 try/except 上不需要 else 块;将代码移到try 块中会更简单,不是吗?
    • 更简单?是的。更好的?根据许多专家的说法,从 GvR 开始。 python.org/dev/peps/pep-0008/#programming-recommendations "另外,对于所有 try/except 子句,将 try 子句限制在绝对必要的最少代码量。同样,这避免了掩盖错误。"
    • @jpmc26 见编辑 2 讨论您的评论。
    • else 块的缩进是否正确?
    • @Teepeemm 是的,else 属于try。如果没有异常则执行。 docs.python.org/3/reference/…
    【解决方案3】:

    是的,您可以使用递归生成器。但是,它们与其他递归函数一样受到递归深度限制。

    def recurse(x):
      yield x
      yield from recurse(x)
    
    for (i, x) in enumerate(recurse(5)):
      print(i, x)
    

    这个循环在崩溃之前达到大约 3000(对我来说)。

    但是,通过一些技巧,您可以创建一个将生成器提供给自身的函数。这允许您编写生成器,就像它们是递归的但不是:https://gist.github.com/3noch/7969f416d403ba3a54a788b113c204ce

    【讨论】:

      【解决方案4】:

      在 Python 3.4 之前,生成器函数在完成时必须引发 StopIteration 异常。 对于递归情况,其他异常(例如IndexError)早于StopIteration,因此我们手动添加。

      def recursive_generator(lis):
          if not lis: raise StopIteration
          yield lis[0]
          yield from recursive_generator(lis[1:])
      
      for k in recursive_generator([6, 3, 9, 1]):
          print(k)
      

      def recursive_generator(lis):
          if not lis: raise StopIteration
          yield lis.pop(0)
          yield from recursive_generator(lis)
      
      for k in recursive_generator([6, 3, 9, 1]):
          print(k)
      

      注意for 循环会捕获StopIteration 异常。 更多关于这个here

      【讨论】:

      【解决方案5】:

      试试这个:

      def recursive_generator(lis):
          yield lis[0]
          yield from recursive_generator(lis[1:])
      
      for k in recursive_generator([6,3,9,1]):
          print(k)
      

      我应该指出这不起作用,因为您的函数中存在错误。它可能应该包括检查lis 不为空,如下所示:

      def recursive_generator(lis):
          if lis:
              yield lis[0]
              yield from recursive_generator(lis[1:])
      

      如果您使用的是 Python 2.7 并且没有 yield fromcheck this question out.

      【讨论】:

      【解决方案6】:

      为什么您的代码没有完成这项工作

      在您的代码中,生成器函数:

      1. 返回(产生)列表的第一个值
      2. 然后它创建一个新的迭代器对象调用相同的生成器函数,将列表的一部分传递给它
      3. 然后停止

      迭代器的第二个实例,递归创建的,永远不会被迭代。这就是为什么你只得到了列表的第一项。

      生成器函数可用于自动创建迭代器对象(实现 iterator protocol 的对象),但随后您需要对其进行迭代:手动调用 next() 方法对象或通过将自动使用迭代器协议的循环语句。

      那么,我们可以递归调用生成器吗?

      答案是是的。现在回到你的代码,如果你真的想用生成器函数来做这件事,我想你可以试试:

      def recursive_generator(some_list):
          """
          Return some_list items, one at a time, recursively iterating over a slice of it... 
          """
          if len(some_list)>1:
          # some_list has more than one item, so iterate over it
              for i in recursive_generator(some_list[1:]):
                  # recursively call this generator function to iterate over a slice of some_list.
                  # return one item from the list.
                  yield i
              else:
                  # the iterator returned StopIteration, so the for loop is done.
                  # to finish, return the only value not included in the slice we just iterated on.
                  yield some_list[0]
          else:
              # some_list has only one item, no need to iterate on it.
              # just return the item.
              yield some_list[0]
      
      some_list = [6,3,9,1]
      for k in recursive_generator(some_list):
          print(k)
      

      注意:项目以相反的顺序返回,因此您可能需要在第一次调用生成器之前使用some_list.reverse()

      在这个例子中需要注意的重要一点是:生成器函数在 for 循环中递归调用自己,它看到一个迭代器并自动在其上使用迭代协议,因此它实际上从中获取值。

      这可行,但我认为这真的没用。我们正在使用生成器函数来迭代一个列表,并且一次只取出一个项目,但是......列表本身就是一个可迭代的,所以不需要生成器! 我当然明白,这只是一个例子,也许这个想法有有用的应用。

      另一个例子

      让我们循环之前的例子(为了懒惰)。假设我们需要打印列表中的项目,将之前项目的数量添加到每个项目中(只是一个随机示例,不一定有用)。

      代码是:

      def recursive_generator(some_list):
          """
          Return some_list items, one at a time, recursively iterating over a slice of it...
          and adding to every item the count of previous items in the list
          """
          if len(some_list)>1:
          # some_list has more than one item, so iterate over it
              for i in recursive_generator(some_list[1:]):
                  # recursively call this generator function to iterate over a slice of some_list.
                  # return one item from the list, but add 1 first. 
                  # Every recursive iteration will add 1, so we basically add the count of iterations.
                  yield i + 1
              else:
                  # the iterator returned StopIteration, so the for loop is done.
                  # to finish, return the only value not included in the slice we just iterated on.
                  yield some_list[0]
          else:
              # some_list has only one item, no need to iterate on it.
              # just return the item.
              yield some_list[0]
      
      some_list = [6,3,9,1]
      for k in recursive_generator(some_list):
          print(k)
      

      现在,如您所见,生成器函数实际上在返回列表项之前做了一些事情,并且递归的使用开始变得有意义。不过,这只是一个愚蠢的例子,但你明白了。

      注意:当然,在这个愚蠢的例子中,列表应该只包含数字。如果您真的想尝试打破它,只需在 some_list 中放入一个字符串,然后玩得开心。同样,这只是一个示例,不是生产代码!

      【讨论】:

      • 非常感谢。整天想知道为什么代码拒绝服从我的命令
      猜你喜欢
      • 1970-01-01
      • 2011-09-13
      • 1970-01-01
      • 2019-08-25
      • 2021-05-22
      • 2020-08-04
      • 2016-05-03
      • 2012-12-30
      相关资源
      最近更新 更多