Python 生成器可以是递归的吗?

Python 生成器可以是递归的吗?,python,recursion,generator,Python,Recursion,Generator,我天真地尝试创建一个递归生成器。没用。这就是我所做的: def recursive_generator(lis): yield lis[0] recursive_generator(lis[1:]) for k in recursive_generator([6,3,9,1]): print(k) 我得到的只是第一件物品6 有没有办法让这些代码正常工作?实际上,在递归方案中将yield命令传输到上面的级别?尝试以下操作: def recursive_generator

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

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

for k in recursive_generator([6,3,9,1]):
    print(k)
我得到的只是第一件物品
6

有没有办法让这些代码正常工作?实际上,在递归方案中将
yield
命令传输到上面的级别?

尝试以下操作:

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

for k in recursive_generator([6,3,9,1]):
    print(k)
我应该指出,这不起作用,因为您的函数中有一个bug。它可能应该包括检查
lis
是否为空,如下所示:

def recursive_generator(lis):
    if lis:
        yield lis[0]
        yield from recursive_generator(lis[1:])
如果您使用的是Python2.7,并且没有从中获得的收益,

为什么您的代码没有完成这项工作 在代码中,生成器功能:

  • 返回(产生)列表的第一个值
  • 然后,它创建一个新的迭代器对象,调用相同的生成器函数,将列表的一部分传递给它
  • 然后停下来
  • 迭代器的第二个实例(递归创建的实例)永远不会被迭代。这就是为什么你只得到清单的第一项

    生成器函数对于自动创建迭代器对象(实现的对象)很有用,但随后需要对其进行迭代:手动调用对象上的
    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)
    
    注意:项目以相反的顺序返回,因此在第一次调用生成器之前,您可能需要使用
    一些list.reverse()

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

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

    另一个例子 让我们重复前面的例子(懒散)。假设我们需要打印列表中的项目,将以前项目的计数添加到每个项目中(只是一个随机示例,不一定有用)

    守则是:

    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)
    
    现在,正如您所看到的,生成器函数实际上在返回列表项之前做了一些事情,递归的使用开始变得有意义。尽管如此,这只是一个愚蠢的例子,但你明白了


    注意:当然,在这个愚蠢的例子中,列表应该只包含数字。如果你真的想去尝试打破它,只需在一些列表中加入一个字符串,并享受乐趣。同样,这只是一个示例,不是生产代码

    递归生成器对于遍历非线性结构非常有用。例如,让二叉树为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 not None
    以捕获其他错误值

    编辑2:关于将递归调用放入try:子句(由@jpmc26注释)

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

    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)
    
    多个日志记录报告可能是噪音大于帮助。如果想要找到坏节点的路径,可能最简单的方法是将每个递归调用封装在自己的try:子句中,并在每个级别使用迄今为止构造的路径提出一个新的ValueError


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

    直到Python 3.4,一个生成器函数在完成时必须引发
    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)
    

    请注意,
    for
    循环将捕获
    StopIteration
    异常。
    更多信息

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

    def recurse(x):
      yield x
      yield from recurse(x)
    
    for (i, x) in enumerate(recurse(5)):
      print(i, x)
    
    这个循环在崩溃之前达到3000左右(对我来说)


    然而,通过一些技巧,您可以创建一个函数,将生成器提供给它自己。这使您可以像编写递归生成器一样编写它们,而不是:

    递归调用只执行一次的原因是您实际上是在创建嵌套的生成器。也就是说,每次递归调用函数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

    当您再次调用它时,您不会让步。它命中第一个yield,看不到另一个yield语句,然后退出。您需要
    从另一个\u生成器()中yield()
    ,或者在while循环中显式地逐个生成每个元素。在您的术语中,
    另一个\u生成器()
    是否是“递归的”——这无关紧要。我看不出是否需要
    上的
    else
    块;将代码移动到
    try
    块会更简单,不是吗?更简单?对更好?许多专家认为,从GvR开始,情况并非如此。此外,对于所有try/except子句,将try子句限制为所需的绝对最小代码量
    def recurse(x):
      yield x
      yield from recurse(x)
    
    for (i, x) in enumerate(recurse(5)):
      print(i, x)
    
    def recursive_generator(lis):
        yield lis[0]
        yield recursive_generator(lis[1:])
    
    for k in recursive_generator([6,3,9,1]):
        print(type(k))