试图了解发电机的递归运行

时间:2019-06-21 18:07:12

标签: python generator yield

我试图弄清楚如何将此代码的运行图绘制到递归树上,因为即使在调试时,我也不确定它的运行方式。 每个产量在做什么,为什么我都需要它们?

我尝试创建一个稍微树状的树,以递归方式将每个运行与下一个运行连接,但我不知道在yield.data之后的内容,其中头部为'c'

class Node:
    def __init__(self, data, next=None):
        self.data = data
        self.next = next


def get_reverse_iterator(head):
    if head.next:
        for datum in get_reverse_iterator(head.next):
            yield datum
    yield head.data


lst = Node('a', Node('b', Node('c')))
for x in get_reverse_iterator(lst):
    print(x)

结果应为: C b 一个

2 个答案:

答案 0 :(得分:1)

要了解其工作原理,您需要了解递归的基本概念。假设我们不处理生成器;我们只希望在给定头节点的情况下反向打印列表的所有节点。我们调用函数 print_reverse ,将节点作为参数。如果节点的下一个字段为空,我们只需打印该字段的数据值。但是,如果下一个不为空,则它指向必须在打印当前节点之前打印的节点。因此,我们再次递归调用 print_reverse 以首先打印该节点。当 print_reverse 返回时,我们现在可以打印当前节点。当然,当我们递归调用 print_reverse 来打印下一个节点时,它可能会发现还有另一个必须首先打印的节点,我们将调用 print_reverse

 SELECT CASE WHEN p.gb_id IS NOT NULL THEN p.gb_id || ' | ' END || CASE WHEN p.aro_id IS NOT NULL THEN p.aro_id || ' | ' END || COALESCE(p.gb_name, '') c
 FROM

在理解发电机问题之前,必须先理解以上代码。我们希望创建一个生成值 的生成器函数,而不是创建打印节点的 data 字段的函数 print_reverse 。因此,重命名函数并用class Node: def __init__(self, data, next=None): self.data = data self.next = next def print_reverse(head): if head.next: print_reverse(head.next) print(head.data) lst = Node('a', Node('b', Node('c'))) print_reverse(lst) 语句替换print函数,并用yield语句递归调用是有意义的:

yield from

现在我们可以使用生成器了,

class Node:
    def __init__(self, data, next=None):
        self.data = data
        self.next = next


def get_reverse_iterator(head):
    if head.next:
        #print_reverse(head.next)
        yield from get_reverse_iterator(head.next)
    #print(head.data)
    yield head.data

lst = Node('a', Node('b', Node('c')))

或:

for x in get_reverse_iterator(lst):
    print(x)

但是使用递归避免创建多个生成器对象的替代方法是:

l = [x in get_reverse_iterator(lst)]

答案 1 :(得分:0)

每当您将该方法作为生成器调用时(例如for x in get_reverse_iterator()),python就会开始逐行执行该方法。每当碰到yield时,它就会停止寒冷并返回。当在next()循环的下一次迭代中要求提供for值时,它将继续执行。

这看起来像一个相当简单的链接列表遍历惯用法,其中列表的每个元素都包含本身就是列表(或其他可迭代值,例如字符串)的数据:

list[0].data = [1, 2, 3, 4]
list[1].data = [5, 6, 7, 8]
...
list[9].data = [37, 38, 39, 40]

因此,代码在此处所做的工作是将这些子列表从主列表的后面打印到主列表的前面。输出应如下所示:

37 38 39 40 33 34 35 36 ... 5 6 7 8 [1, 2, 3, 4]

当您查看代码的执行方式时,这一点显而易见。我用文字重写它:

func get_reverse_iterator(head) {
    if head isn't the last element of the list, then
        call this function on the next element of the list (head.next)
        for every element in the return value of that,
            yield that element
    yield this element's data

“基本情况”是列表的最后一个元素,没有.next。因此,它的data(可迭代)将返回到倒数第二个元素。倒数第二个元素依次产生该数据的每个元素,然后将其自身的数据返回到倒数第三个元素。倒数第二个元素依次产生该数据的每个元素,依此类推,直到最后到达列表的第一个元素。到目前为止,每个单独的yield语句都递归地在链上传递了一个元素,因此到目前为止,第一个元素的内部for循环产生了36个值。最后,列表中所有后面的元素都完成了传递值的操作,因此第一个元素到达函数的最后一条语句并产生自己的数据。

但是没有什么可以捕获产生的数据并按单个元素进行解析的了,因此它首先以list的形式打印出来。或者,至少在上面的示例中如此。


在您的情况下,它更直接了,因为当您遍历string时,每个项目仍然是string。但这是较小规模的事情:

    get_reverse_iterator()的根节点上调用
  1. lst
  2. 根节点(我将其称为NodeA)具有.next
  3. 在下一个节点上调用
  4. get_reverse_iterator(),我将其称为NodeB
  5. NodeB有一个.next
  6. 在下一个节点上调用
  7. get_reverse_iterator(),我将其称为NodeC
  8. NodeC没有.next
  9. get_reverse_iterator(NodeC)跳过for循环并产生NodeC.data,即'c'`
  10. get_reverse_iterator(NodeB)'c'循环中捕获for并产生
  11. get_reverse_iterator(NodeA)'c'循环中捕获for并产生
  12. 'c'被分配给x,并被打印。
  13. 发生外循环的下一次迭代,执行返回到get_reverse_iterator(NodeB)
  14. for循环结束,因为get_reverse_iterator(NodeC)已停止产生东西
  15. get_reverse_iterator(NodeB)结束for循环,退出if块,最后产生NodeB.data,即'b'
  16. get_reverse_iterator(NodeA)'b'循环中捕获for并产生
  17. 'b'被分配给x,并被打印。
  18. 发生外循环的下一次迭代,执行返回到get_reverse_iterator(NodeA)
  19. for循环结束,因为get_reverse_iterator(NodeC)已停止产生东西
  20. get_reverse_iterator(NodeA)结束for循环,退出if块,最后产生NodeA.data,即'a'
  21. 'a'被分配给x,并被打印
  22. 外部for循环结束,因为get_reverse_iterator(NodeA)已停止产生东西。