本來想這周繼續(xù)寫寫 Flask 那點(diǎn)破事兒的,但是想了想決定換換口味,,來聊聊很不容易理解但是很重要的 Python中的生成器和協(xié)程,。 我猜大家對于生成器肯定并不陌生,但是為了能讓我愉快的繼續(xù)裝逼,,我們還是用點(diǎn)篇幅講一下什么是生成器吧,。比如在 Python 里,我們想生成一個(gè)范圍 (1,100000) 的一個(gè) list,,于是我們無腦寫了如下的代碼出來 注1:這里有同學(xué)提出了為什么我們不直接返回 range(start,stop) ,,Nice question,這里涉及到一個(gè)基礎(chǔ)問題,, range 的機(jī)制究竟是怎樣的,。這就要分版本而論了,在 Python 2.x 的版本中,, range(start,stop)其實(shí)本質(zhì)上是預(yù)先生成一個(gè) list ,而 list 對象是一個(gè) Iterator ,,因此可以被 for 語句所使用。 在 Python 3 中事情發(fā)生了一點(diǎn)變化,可能社區(qū)覺得 range 和 xrange 分裂太過蛋疼,,于是將其合并,,于是現(xiàn)在在 Python 3 中,,取消了 xrange 的語法糖,然后 range 的機(jī)制也變成生成一個(gè) Generator 而不是list 但是大家考慮過一個(gè)問題么,,如果我們想生成數(shù)據(jù)量非常大,,預(yù)先生成數(shù)據(jù)的行為無疑是很不明智的,,這樣會耗費(fèi)大量的內(nèi)存,。于是 Python 給我們提供了一種新的姿勢,Generator (生成器),。 是的,,Generator 其中一個(gè)特性就是不是一次性生成數(shù)據(jù),而是生成一個(gè)可迭代的對象,,在迭代時(shí),,根據(jù)我們所寫的邏輯來控制其啟動時(shí)機(jī)。 這里可能有一個(gè)問題,,大家肯定想問 Python 開發(fā)者們不可能為了這一種使用場景而去單獨(dú)創(chuàng)建一個(gè) Generator 機(jī)制吧,,那么我們 Generator 還有其余的使用場景么。當(dāng)然,,請看標(biāo)題,,對了嘛,Generator 另一個(gè)很大作用可以說就是當(dāng)做協(xié)程使用,。不過在這之前,,我們要去深入的了解下 Generator 才能方便我們后面的講解。 關(guān)于 Python 中可迭代對象的一點(diǎn)背景知識 首先,,我們來看看 Python 中的迭代過程,。 好了,讓我們來看看上面這段代碼里發(fā)生了什么,,首先 for 語句的引用首先去判斷迭代的是 Iterable 對象還是Iterator 對象,,如果是實(shí)現(xiàn)了 __iter__ 方法的對象,,那么就是一個(gè) Iterable 對象, for 循環(huán)首先調(diào)用對象的__iter__ 方法來獲取一個(gè) Iterator 對象,。那么什么是 Iterator 對象呢,,這里可以近似的理解為是實(shí)現(xiàn)了 next() 方法(注:在Python3中是 next 方法)。 OK,,讓我們繼續(xù)回到剛剛說到的那里,,在上面的代碼中 for 語句首先判斷是一個(gè) Iterable 對象還是 Iterator 對象,如果是 Iterable 對象那么調(diào)用其 iter 方法來獲取一個(gè) Iterator 對象,,接著 for 循環(huán)會調(diào)用 Iterator 對象中的next() (注:Python3 里是 __next__ )方法來進(jìn)行迭代,,直到迭代過程結(jié)束拋出 StopIteration 異常。 讓我們先看看前面那段代碼吧: 首先我們要確定一點(diǎn)的是 Generator 其實(shí)也是一個(gè) Iterator 對象,。OK 讓我們來看看上面這段代碼,,首先 for 確定 generateList1 是一個(gè) Iterator 對象,然后開始調(diào)用 next() 方法進(jìn)行進(jìn)一步迭代,。OK 此時(shí)你肯定想問這里面 next() 方法是怎樣讓 generateList1 進(jìn)一步往下迭代的呢,?答案在于 Generator 的內(nèi)建 send() 方法。我們還是來看一段代碼,。 這里我們應(yīng)該輸出什么,?答案就是 0,1,2,3,4 ,結(jié)果上和我們用 for 循環(huán)進(jìn)行運(yùn)算的結(jié)果是不是一樣,。好了,,我們現(xiàn)在可以得出一個(gè)結(jié)論就是: Generator 迭代的本質(zhì)就是通過內(nèi)建的 next() 或 __next__() 方法來調(diào)用內(nèi)建的 send() 方法。 前面我們提到一個(gè)結(jié)論 : Generator 迭代的本質(zhì)就是通過內(nèi)建的 next() 或 __next__() 方法來調(diào)用內(nèi)建的 send() 方法,。 現(xiàn)在我們來看個(gè)例子: 好了這段代碼的輸出應(yīng)該是什么,? 簡而言之就是,當(dāng)我們調(diào)用 send() 函數(shù)的時(shí)候,,我們 send(x) 的值會發(fā)送給 newvalue 向下繼續(xù)執(zhí)行直到遇到下一次 yield 的出現(xiàn),,然后返回值作為一個(gè)過程的結(jié)束。然后我們的 Generator 靜靜的沉睡在內(nèi)存中,,等待下一 注2:有同志問:“這里沒想明白,c.send(3) 是 相當(dāng)于 yield n 返回了個(gè) 3 給 newvalue ?”,,好的,,nice question,,其實(shí)這個(gè)問題我們看前面之前的代碼運(yùn)行圖就知道, c.send(3) 首先,,將 3 賦值給 newvalue,,然后程序運(yùn)行剩下的代碼,,直到遇到下一個(gè) yield 為止,那么在這里,,我們運(yùn)行剩下完代碼,,在遇到y(tǒng)iled n 之前,將 n 的值已經(jīng)改變?yōu)?3 ,接著,, yield n 即約等于 return 3 。接著 countdown 這個(gè)Generator 將所有變量的狀態(tài)凍結(jié),,然后靜靜的呆在內(nèi)存中,,等待下一次的 next 或 __next__() 方法或者 小貼士:我們?nèi)绻苯诱{(diào)用 send() 的話,,第一次請務(wù)必 send(None) 只有這樣一個(gè) Generator 才算是真正被激活了,。我們才能進(jìn)行下一步操作。
Coroutines are computer program components that generalize subroutines for nonpreemptive multitasking,by allowing multiple entry points for suspending and resuming execution at certain locations. Coroutines are well-suited for implementing more familiar program components such as cooperative tasks, exceptions,event loop, iterators, infinite lists and pipes. 簡而言之,,協(xié)程是比線程更為輕量的一種模型,我們可以自行控制啟動與停止的時(shí)機(jī),。在 Python 中其實(shí)沒有專門針對協(xié)程的這個(gè)概念,,社區(qū)一般而言直接將 Generator 作為一種特殊的協(xié)程看待,想想,,我們可以用 next 或__next__() 方法或者是 send() 方法喚醒我們的 Generator ,,在運(yùn)行完我們所規(guī)定的代碼后, Generator 返回并將其所有狀態(tài)凍結(jié),。這是不是很讓我們 Excited 呢?。?/span> 現(xiàn)在我們要后序遍歷二叉樹,,我知道看這篇文章神犇們都能無腦寫出來的,,讓我們看看代碼先: 但是,我們知道遞歸深度太深的話,,我們要么爆棧要么 py 交易失敗,,OK ,Generator 大法好,,把你碼農(nóng)平安保,。 還是直接看代碼: 看起來很復(fù)雜是不是,?沒事當(dāng)做課后作業(yè),,大家可以在評論里給我留言,我們一起進(jìn)行一下 py 交易吧~ 參考資料 1,、提高你的Python: 解釋‘yield’和‘Generators(生成器)’ |
|