還記得在迭代器裡我們說為什麼將列表轉為迭代器麼?( )
小明:因為列表太大的話佔用內存太大,做成迭代器可以節省空間,用的時候再拿出部分
是的,今天要講的生成器是不會把結果保存在一個系列中,而是保存生成器的狀態。
在每次進行迭代時返回一個值,直到遇到StopIteration異常結束。
見過這種東西吧:
你可以認為每一杯飲料就是一個生成的對象,我不會一次倒出所有的飲料
而是要喝的時候去倒出一杯(也就是需要的時候生成一個)
簡單方法創建生成器
我們看個例子:
我們發現,當要生成的list非常大時,拋出異常,存儲報錯。
那怎樣生成這種巨大的list呢?
你親手試一下,發現瞬間程序就運行結束了
我們看到,b是一個generator,也就是生成器模式
你應該已經注意到,生成器的創建很簡單,將列表生成式的中括號改成小括號即可
注意:這裡說的不是列表,因為列表的中括號改成小括號是元組!
那我們怎麼生成一個內容呢?
和之前的迭代器相同,使用next()函數即可:
直到最後會拋出異常,也就是到達了生成器的末端了
函數進化為生成器
還記得函數的定義麼?
我們在之前用遞歸定義了一個斐波那契數列
現在我們定義一個新的函數來生成斐波那契數列的第n項
為了實現後一項等於前兩項之和使用了a,b = b,a+b
為什麼這樣寫,留給大家思考~
提示:可以輸入n=3,自己感受一下調用函數過程中a和b的變化
值得注意的是,這個函數,當n=0時返回的是1,而不是正確的0
所以我們對其進行修改:
在循環之前,加了一個判斷
小明:老溼!你這個說的還是函數啊,和生成器有啥關係?說好的函數進化成生成器呢?
好的,我們看看函數怎麼進化為生成器!
我們把函數中的return換成yield
函數就進化成了生成器,當我們調用時,發現返回的是生成器對象
為了拿到數據,我們應該怎麼做呢?
小紅:老師,是不是可以試試next()函數呢?
對,不過在此之前,我們先要用一個變量去接收這個生成器對象
並且為了觀察生成器的特點,我們對函數進行修改!
仔細看好:
當我們使用next(a)對生成器操作一次時,會返回循環一次的值
也就是在yield處結束本次運行
但它的特點就是下次使用next(a)時,接著上次的斷點繼續運行,直到下一個yield
不斷使用next(a),直到運行到生成器結尾處,如下圖:
可能你對他的運行過程還不是特別清晰
我們加上print輸出來徹底搞懂他的運行過程:
發現每次返回值都是在yield的地方了吧~
小結:
- 講了兩種生成器創建方式
- 加了yield的函數就變成了生成器
- 要定義一個變量接收生成器的返回值
- 使用next()獲取生成器每次返回的值,並且斷點在yield處
- 下次使用next()從上次的斷電往下執行,直到生成器末端(這裡表現為循環結束)
- 生成器屬於迭代器,所以肯定是可迭代對象啦~
使用for循環調用生成器
我們使用next()去遍歷生成器的時候,我們不知道什麼時候會結束
而結束後再使用next()會拋出異常
因為生成器屬於迭代器
所以我們可以使用for循環去調用生成器
與next()等價的方式
send()強勢入場
我們先看一個例子:
發現每次運行,除了返回下一個,還會打印出None
觀察代碼,注意 item = yield i 這句
首先執行等號右邊的,yield返回,此時,返回生成器一個對象,並且中斷
在下次使用 f.__next__( )時候,並沒有傳內容進去,所以可以認為yield i 這整個賦值給item的為None
所以item打印出為None
為了做個比較,引入send()
send()可以看做next()的增強版
除了可以使用next()功能
還能傳入一個值到上次yield斷開地方的整體表達式(這裡傳給是yield i)
send()的坑:
這是為什麼呢?
6
send()退化為next()
小紅:老師,我想了兩天終於想到了!
哦?你說說
小紅:因為 f.send()取生成器第一個對象,並且傳入了一個參數‘Python’
但是,並沒有變量去接收這個參數!所以報錯了!
是的,之前我們的代碼是這樣的:
注意,我們調用第一個對象使用next,並沒有傳入參數!
而第二次使用send調用下一個對象時候,傳入的參數相當於代替了yield i
也就是賦值給了item,所以沒報錯
小明:老溼,你說send是加強版的next,我想給send退化到next,可以麼?
這個嘛,是可以的!小明你試試!
小明:大家都退後!我要裝逼了!
小明:不傳參數竟然不行!!!
小明啊,應該是這樣的!
傳入None才對!
小明:我不服!老溼,你說這生成器很厲害,具體有啥應用呢?學了沒用豈不是很雞肋?
這個嘛!其實是有用的,我先劇透一下!
多任務——協程
我們先介紹一下多任務
多任務處理是指用戶可以在同一時間內運行多個應用程序,每個應用程序被稱作一個任務
簡單點說,就是
你現在可能邊看這篇文章邊聽著音樂
而看文章是一個任務(這裡的任務指正在做的事情)
聽音樂也是一個任務
你同時在做這兩件事
就是多任務啦
電腦和人還是不一樣的
我們今天講一下協程來完成多任務(之後還會講到線程、進程來完成多任務)
考慮一個工廠流水線
A機器每次將一件貨物放入箱子
B機器每次將A機器的箱子封箱打包
為了不產生問題,必須A完成一個,B接著完成一個
並且看起來兩個任務是同時執行的!
在Python中可以用生成器實現簡單的協程:
我們來看這個程序,先定義了兩個生成器(不是函數哦~原因在之前課程講過)
當我們在while主程序中,先使用f1.__next__( )調用生成器func1,因為fun1的循環條件始終為真
所以先打印(執行裝入操作)然後遇到 yield 退出生成器func1,回到主程序
接著執行f2.__next__( )調用生成器func2,像之前調用func1一樣,先打印(執行打包操作)
然後遇到yield退出生成器func2,回到主程序
因為主程序循環條件始終為真,所以繼續像之前一樣,接著使用f1.__next__( )調用生成器func1。。。
如此往復。。。
我是使用打斷來停止程序執行的,不然會不斷執行下去
由於兩個生成器(任務)交替執行,很快
就像在多任務執行
所以,通俗理解看上去同時執行的就是多任務~
小明:竟然有這種操作!
今天作業:
敲一遍代碼,理解一下兩種生成器創建方式
自己不敲代碼永遠學不會寫代碼
下課
人生苦短,我選Python
未完待續,連載中......
閱讀更多 燈塔大數據 的文章