递归生成器(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,
最后,
要注意的是每次
總結
以上是生活随笔為你收集整理的递归生成器(recursion/recurrence generator)个人理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DataSource
- 下一篇: Tomcat安装及运行