递归生成器(recursion/recurrence generator)个人理解

先上完整代码,
在这里插入图片描述

接下来是控制台里的代码结果输出,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面是,为了更好的跟踪程序的执行,在代码中插入多个print()后,在控制台的输出
在这里插入图片描述
第一阶段:
瀑布下流
在这里插入图片描述
第二阶段:
逆流而上,
在这里插入图片描述
可以看出,自下而上,从yield nestedList 开始,向上返回了 2 次,所以,三个等号被打印了 2 次,每次返回都触发了 3 个打印的 语句

所以有了 在这里插入图片描述的结果

这里只是粗略的画了2个图,希望大家能看懂,以后有时间我再来继续完善这篇文章,
October the 28th 2022 Friday ,
再补充几点,
1.递归(recursion / recurrent)就是有去有回,你调用自己就相当于出去了,那么在完成了任务之后,还得回来,这就是我的理解,
2.生成器是一种特殊的迭代器,所谓迭代器,就是可以遍历的容器,比如:for i in range(10):
就会得到 0~9 十个值,那么range(10)就是一个可以被迭代的容器,之所以叫做容器,是因为容器里面可以装很多东西,在编程里,容器就装了很多个元素(值),range(10)是一个迭代器,可以使用 for 语句 遍历并输出 range(10)这个迭代器里的所有元素(值),
3.生成器相较于迭代器特殊的地方在于,生成器内部的语句可以被yield暂停,直到生成器被再次唤醒,生成器内部的语句才会接着上次暂停的地方继续向下执行,
4.生成器另一个特殊的地方在于,生成器是从生成器模板来的,而生成器模板是一个包含yield语句的函数(方法),
举个例子:
def enumList(nestedList):
try:
for subList in nestedList:
for element in enumList(subList):
yield element
enumList,看起来是用 def 关键字 定义的一个函数,但实际上,一旦一个函数内部包含了yield语句,它就不再是一个函数了,而是
生成器的模板,所以,enumList 就是一个生成器的模板,
而 enumList(nestedList) 不是调用函数,而是用 生成器模板 enumList 做出了一个生成器 ,使用 enumList(nestedList) 不是很方便
所以,通常将做出的生成器 赋值给一个变量,例如:
enu=enumList(nestedList) enu就变成了一个生成器了
print(enu,type(enu))
===》输出结果是:
<generator object enumList at 0x000001FAE6276430>
<class ‘generator’>
可以看到,enu 是 生成器 即 generator ,且 enu 是一个具体的 生成器的一个 实例,有内存地址 at 0x000001FAE6276430
5.yield 作用是,
(1)暂停程序,也就是它下面那行开始的所有语句都停止执行,
(2)同时,将它后面的 值 给 在等待这个值的 接收者 送过去,一个有缘人,而在本文中,接收 yield 后面跟着的那个值的 始终是 element,这个变量。
举个简单的例子来理解一下yield 作用,和本文的难点
for element in enumList(subList):
1.element想从生成器 enumList(subList)获得一个值,上面我们说过了,enumList(subList)它不是在调用函数enumList(subList),因为enumList中包含了yield语句,所以它就不再是一个普通的程序了,而是变成了一个生成器的模板,enumList(subList)是做出了一个生成器,
那么,这个生成器里就应该有一些值(元素),所以,for element in enumList(subList):的意思是,element想从生成器 enumList(subList)获得一个值,
2.但是,生成器enumList(subList)得根据 subList 提供的东西 ,看人下菜碟的给 element想要的值
def enumList(nestedList):
try:
for subList in nestedList:
for element in enumList(subList):
yield element
except TypeError:
yield nestedList
经过3次递归,即在第3次递归的时候,nestedList变成了1,而 for subList in nestedList: 是错误的,因为 1 不能被迭代,所以会报
TypeError,所以就被 except捕获, 进入了 except TypeError:代码段中,执行,yield nestedList ,即返回 nestedList 的值,
我们知道,在这次生成器enumList(nestedList)的执行中 nestedList =1,所以,yield 把1 送出去,并暂停 本次生成器enumList(nestedList)其他语句的执行,
那么关键的地方来了,这个1 送给谁了,结合上文,我们就清楚了,是element在等着这个值,
我们再捋一下,
在第3次递归的时候,也就是,执行 def enumList(nestedList): 的时候,由于 1 是不可以被迭代的,所以,程序 进入了except 代码块中,并最终返回了 nestedList 当时的值 1,也就是说,执行 def enumList(nestedList): 的结果是 得到了一个1,
那么这个 1 就被送到了 第二次 递归语句中的 element ,那么,因为element拿到了值 1,所以会执行 yield element 语句,所以,element 的值 1 被yield 送到了 第一次递归 语句中的 element 处 ,所以,第一次递归中 的 element 也拿到了 它想要的值 1,所以会 顺序执行 其下的语句 yield element,而,这是最外面的递归程序了,所以,我们就能看到 通过 print(next(enu),‘01st call’) 调用 生成器 enumList(nestedList)得到最终的结果 1 01st call

先这么着吧,面要陀了,以后有时间我再来补充完善。

接着说,
13:22 22/10/28
刚才继续看代码,发现,上面落下了一个非常非常重要的点,
就是,yield 所处的位置,对于代码的运行是有着至关重要的作用的,
这里就不画图了,用文字干说,

1.先看第三次递归中,
def enumList(nestedList):
try:
for subList in nestedList:
for element in enumList(subList):
yield element
except TypeError:
yield nestedList
第三次 递归中,yield 返回的是 nestedList, yield 所处的位置是程序的最末一行,因此,第三次递归中所有的语句都执行完毕了,所以,当再次 next()的时候,程序不会在第三次递归中继续运行,而是会在第二次递归中找 yield

2.接着看第二次递归,
def enumList(nestedList):
try:
for subList in nestedList:
for element in enumList(subList):
yield element
except TypeError:
yield nestedList
第二次递归,返回的是 element ,yield 所处的位置是,内层 for 语句块中,在被next() 语句激活后,按道理会接着执行 for element in enumList(subList):的第二次循环,但,上面第三次递归中,只能返回一个值,所以,内层 for 语句没有东西可以执行了,因此就退出了 内层 for 语句,进行外层 for语句的 第二次循环,我们知道,在第二次递归中,nestedList=[1,2,3],所以,第二次递归中 外层 for语句的第二次循环,subList 拿到了2这个值,于是进入 第三次递归中,for subList in nestedList:报错,因为此时 nestedList=2,所以进入第三次递归中的except TypeError:语句,遇到 yield nestedList,(1)冻结程序,(2)将 nestedList=2 返回给第二次递归中的 element ,第二次递归中的element 有值了为2 所以可以继续执行 内层 for语句的 yield element 语句,1)冻结程序,(2)将 element =2 送到第一次递归 中的 element ,第一次递归 中的 element 拿到了 值2 ,就 具备条件 可以继续执行 内层 for语句 的 yield element 语句 ,1)冻结程序,(2)将将 element 的值 2 送出,由于这时已经是最外面的递归了,所以 element 的值 2 送出后,被 next()接收,完成第二次 next()调用

所以, 本篇文章的精华是,第二次 next()调用,因为它要接着所有的yield去执行,而有的yield 处于 for 循环中( yield element),所以就必须接着上次的循环开始下一次循环,而有的 yield 不在 for 循环中而是处于末尾的位置,因此没有什么可做的话就会自然结束工作,就是说完成了对这个 yield 的处理,暂时不用管它了,接着去处理其他的 yield 就好了。

另外,也要注意,递归是有层次的,每次递归时,都会有yield参与其中,处理的方式方法是不同的,
1.递归的层次,2.yield的位置,这两点搞清楚了,一切就迎刃而解了。
豁然开朗。

13:57 22/10/28
继续补充,我又想到了一个好的理解方式,
拿 第一次 的 next()举例
for element in enumList(subList):
print(‘===’)
print(‘15 line element=’,element)
yield element
我们知道 element 是要 朝 enumList(subList) 要东西的, 而 第三次递归的结果只有一个 1,也就说 ,在第二次递归中, enumList(subList)只有一个元素 1,所以 在第二次 next() 虽然激活了 第二次递归(每次next()调用都会有2到3次递归,这里不要糊涂了)中的 yield ,但由于此时的 for 语句 里的 enumList(subList)只有一个 值 1,所以就没有新的循环开始,因此,虽然 此处的 yield 在 for 语句中,也没法开始下一次循环, 因此,内层 的 for 也就无奈的只好结束了。退出内层 for 语句 回到 外层的 for语句中 ,做该做的事情。

15:13 22/10/28
继续补充
在这里插入图片描述
November the 03rd Thursday 2022

今天继续来补充,相信这次可以说明白这个问题,如果,有同学不想被误导,上面的可以直接忽略,从这里看就行。

首先,
第一个重点是:对yield语句的理解,当遇到 yield语句,它会做两件事,
1.暂停 yield所在的程序段所有语句的执行,相当于冻住,
2.将yield 后面的变量值送出去,谁调用了包含yield的代码块,就送给谁,
第二个重点是:对递归的理解,递归就是自己调用自己,是有层次的,最外层在等待它内部的那层自己的返回值,可以看做是俄罗斯套娃,从外向内时,只有将外层套娃打开,才能看到内层的套娃,以此类推,从内向外时,只有将内层的套娃处理完包好,才能继续处理外层的套娃,以此类推。
所以递归的时候,总的来看是:外面的程序等待里面的程序给出结果,
换言之:只有里面的程序有了结果,才能接着处理外面的程序,
知道这个就够用了。
其次,
拿上面这段‘将多维列表展平’的程序来说,还需要注意的一点是:递归yield搭配一起使用的时候,每层递归里都有yield,所以,每层递归与其他层的yield是不同的,就像要处理每层递归一样,也要按顺序逐个处理每层递归中的yield
yield是有顺序的,每次调用 next() 的时候,都要顺序处理上次调用 next()时遇到的各个yield
处理的顺序是:本次调用 next()要最先处理上次调用 next()时遇到的第一yield,然后本次调用 next()才应该去处理,上次调用 next()时遇到的第二yield,也就是说,每次调用 next()时,都要首先处理上次调用时遇到的最早的那个yield,看起来的顺序是从内往外处理yield
最后,
要注意的是每次

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值