深度好文:Python之列表生成式、生成器、可迭代對(duì)象與迭代器(二)

作者:云游道士
原文:https://www.cnblogs.com/yyds/p/6281453.html
三、生成器(Generator)
從名字上來看,生成器應(yīng)該是用來生成數(shù)據(jù)的。
1. 生成器的作用
按照某種算法不斷生成新的數(shù)據(jù),直到滿足某一個(gè)指定的條件結(jié)束。
2. 生成器的構(gòu)造方式
構(gòu)造生成器的兩種方式:
使用類似列表生成式的方式生成 (2*n + 1 for n in range(3, 11))
使用包含yield的函數(shù)來生成
如果計(jì)算過程比較簡(jiǎn)單,可以直接把列表生成式改成generator;但是,如果計(jì)算過程比較復(fù)雜,就只能通過包含yield的函數(shù)來構(gòu)造generator。
說明:Python 3.3之前的版本中,不允許迭代函數(shù)法中包含return語句。
3. 生成器構(gòu)造實(shí)例
# 使用類似列表生成式的方式構(gòu)造生成器g1 = (2*n + 1 for n in range(3, 6))# 使用包含yield的函數(shù)構(gòu)造生成器def my_range(start, end):for n in range(start, end):yield 2*n + 1g2 = my_range(3, 6)print(type(g1))print(type(g2))
輸出結(jié)果:
<class 'generator'><class 'generator'>
4. 生成器的執(zhí)行過程與特性
生成器的執(zhí)行過程:
在執(zhí)行過程中,遇到y(tǒng)ield關(guān)鍵字就會(huì)中斷執(zhí)行,下次調(diào)用則繼續(xù)從上次中斷的位置繼續(xù)執(zhí)行。
生成器的特性:
只有在調(diào)用時(shí)才會(huì)生成相應(yīng)的數(shù)據(jù)
只記錄當(dāng)前的位置
只能next,不能prev
5. 生成器的調(diào)用方式
要調(diào)用生成器產(chǎn)生新的元素,有兩種方式:
調(diào)用內(nèi)置的next()方法
使用循環(huán)對(duì)生成器對(duì)象進(jìn)行遍歷(推薦)
調(diào)用生成器對(duì)象的send()方法
實(shí)例1:使用next()方法遍歷生成器
print(next(g1))print(next(g1))print(next(g1))print(next(g1))
輸出結(jié)果:
7911Traceback (most recent call last):File "***/generator.py", line 26, in <module>print(next(g1))StopIteration
print(next(g2))print(next(g2))print(next(g2))print(next(g2))
輸出結(jié)果:
7911Traceback (most recent call last):File "***/generator.py", line 31, in <module>print(next(g2))StopIteration
可見,使用next()方法遍歷生成器時(shí),最后是以拋出一個(gè)StopIeration異常終止。
實(shí)例2:使用循環(huán)遍歷生成器
for x in g1:print(x)for x in g2:print(x)
兩個(gè)循環(huán)的輸出結(jié)果是一樣的:
7911
可見,使用循環(huán)遍歷生成器時(shí)比較簡(jiǎn)潔,且最后不會(huì)拋出一個(gè)StopIeration異常。因此使用循環(huán)的方式遍歷生成器的方式才是被推薦的。
需要說明的是:如果生成器函數(shù)有返回值,要獲取該返回值的話,只能通過在一個(gè)while循環(huán)中不斷的next(),最后通過捕獲StopIteration異常
實(shí)例3:調(diào)用生成器對(duì)象的send()方法
def my_range(start, end):for n in range(start, end):ret = yield 2*n + 1print(ret)g3 = my_range(3, 6)
print(g3.send(None))print(g3.send('hello01'))print(g3.send('hello02'))
輸出結(jié)果:
7hello019hello0211
print(next(g3))print(next(g3))print(next(g3))
輸出結(jié)果:
7None9None11
結(jié)論:
next()會(huì)調(diào)用yield,但不給它傳值
send()會(huì)調(diào)用yield,也會(huì)給它傳值(該值將成為當(dāng)前yield表達(dá)式的結(jié)果值)
需要注意的是:第一次調(diào)用生成器的send()方法時(shí),參數(shù)只能為None,否則會(huì)拋出異常。當(dāng)然也可以在調(diào)用send()方法之前先調(diào)用一次next()方法,目的是讓生成器先進(jìn)入yield表達(dá)式。
6. 生成器與列表生成式對(duì)比
既然通過列表生成式就可以直接創(chuàng)建一個(gè)新的list,那么為什么還要有生成器存在呢?
因?yàn)榱斜砩墒绞侵苯觿?chuàng)建一個(gè)新的list,它會(huì)一次性地把所有數(shù)據(jù)都存放到內(nèi)存中,這會(huì)存在以下幾個(gè)問題:
內(nèi)存容量有限,因此列表容量是有限的;
當(dāng)列表中的數(shù)據(jù)量很大時(shí),會(huì)占用大量的內(nèi)存空間,如果我們僅僅需要訪問前面有限個(gè)元素時(shí),就會(huì)造成內(nèi)存資源的極大浪費(fèi);
當(dāng)數(shù)據(jù)量很大時(shí),列表生成式的返回時(shí)間會(huì)很慢;
而生成器中的元素是按照指定的算法推算出來的,只有調(diào)用時(shí)才生成相應(yīng)的數(shù)據(jù)。這樣就不必一次性地把所有數(shù)據(jù)都生成,從而節(jié)省了大量的內(nèi)存空間,這使得其生成的元素個(gè)數(shù)幾乎是沒有限制的,并且操作的返回時(shí)間也是非常快速的(僅僅是創(chuàng)建一個(gè)變量而已)。
我們可以做個(gè)試驗(yàn):對(duì)比一下生成一個(gè)1000萬個(gè)數(shù)字的列表,分別看下用列表生成式和生成器時(shí)返回結(jié)果的時(shí)間和所占內(nèi)存空間的大小:
import timeimport systime_start = time.time()g1 = [x for x in range(10000000)]time_end = time.time()print('列表生成式返回結(jié)果花費(fèi)的時(shí)間:%s' % (time_end - time_start))print('列表生成式返回結(jié)果占用內(nèi)存大小:%s' % sys.getsizeof(g1))def my_range(start, end):for x in range(start, end):yield xtime_start = time.time()g2 = my_range(0, 10000000)time_end = time.time()print('生成器返回結(jié)果花費(fèi)的時(shí)間:%s' % (time_end - time_start))print('生成器返回結(jié)果占用內(nèi)存大小:%s' % sys.getsizeof(g2))
輸出結(jié)果:
列表生成式返回結(jié)果花費(fèi)的時(shí)間:0.8215489387512207
列表生成式返回結(jié)果占用內(nèi)存大小:81528056
生成器返回結(jié)果花費(fèi)的時(shí)間:0.0
生成器返回結(jié)果占用內(nèi)存大小:88
可見,生成器返回結(jié)果的時(shí)間幾乎為0,結(jié)果所占內(nèi)存空間的大小相對(duì)于列表生成器來說也要小的多。
四、可迭代對(duì)象(Iterable)
我們經(jīng)常在Python的文檔中看到“Iterable”這個(gè)此,它的意思是“可迭代對(duì)象”。那么什么是可迭代對(duì)象呢?
可直接用于for循環(huán)的對(duì)象統(tǒng)稱為可迭代對(duì)象(Iterable)。
目前我們已經(jīng)知道的可迭代(可用于for循環(huán))的數(shù)據(jù)類型有:
集合數(shù)據(jù)類型:如list、tuple、dict、set、str等
生成器(Generator)
可以使用isinstance()來判斷一個(gè)對(duì)象是否是Iterable對(duì)象:
from collections import Iterableprint(isinstance([], Iterable))
五、迭代器(Iterator)
1. 迭代器的定義
可以被next()函數(shù)調(diào)用并不斷返回下一個(gè)值的對(duì)象稱為迭代器:Iterator。
很明顯上面講的生成器也是迭代器。當(dāng)然,我們可以使用isinstance()來驗(yàn)證一下:
from collections import Iteratorprint(isinstance((x for x in range(5)), Iterator))
輸出結(jié)果為:True
2. 對(duì)迭代器的理解
實(shí)際上,Python中的Iterator對(duì)象表示的是一個(gè)數(shù)據(jù)流,Iterator可以被next()函數(shù)調(diào)用被不斷返回下一個(gè)數(shù)據(jù),直到?jīng)]有數(shù)據(jù)可以返回時(shí)拋出StopIteration異常錯(cuò)誤。可以把這個(gè)數(shù)據(jù)流看做一個(gè)有序序列,但我們無法提前知道這個(gè)序列的長(zhǎng)度。同時(shí),Iterator的計(jì)算是惰性的,只有通過next()函數(shù)時(shí)才會(huì)計(jì)算并返回下一個(gè)數(shù)據(jù)。
生成器也是這樣的,因?yàn)樯善饕彩堑鳌?/span>
六、Iterable、Iterator與Generator之間的關(guān)系
生成器對(duì)象既是可迭代對(duì)象,也是迭代器:?我們已經(jīng)知道,生成器不但可以作用與for循環(huán),還可以被next()函數(shù)不斷調(diào)用并返回下一個(gè)值,直到最后拋出StopIteration錯(cuò)誤表示無法繼續(xù)返回下一個(gè)值了。也就是說,生成器同時(shí)滿足可迭代對(duì)象和迭代器的定義;
迭代器對(duì)象一定是可迭代對(duì)象,反之則不一定:?例如list、dict、str等集合數(shù)據(jù)類型是可迭代對(duì)象,但不是迭代器,但是它們可以通過iter()函數(shù)生成一個(gè)迭代器對(duì)象。
也就是說:迭代器、生成器和可迭代對(duì)象都可以用for循環(huán)去迭代,生成器和迭代器還可以被next()方函數(shù)調(diào)用并返回下一個(gè)值。
覺得不錯(cuò),點(diǎn)個(gè)在看唄!
