Python迭代器與生成器

原文來自廖雪峰Python3教程高級特性部分整胃,略有增刪改赋秀。

我整理本文的目的是有這樣的問題:在Python中,for循環(huán)極為常用待榔,我們利用它進行批量提取數(shù)據(jù)或進行數(shù)據(jù)運算逞壁。但較為深入的概念和機制我們是否真正有所理解呢?

可迭代對象與迭代器

可以直接作用于for循環(huán)的數(shù)據(jù)類型有以下幾種:

  • 一類是集合數(shù)據(jù)類型,如list猾担、tuple袭灯、dictset绑嘹、str等稽荧;
  • 一類是generator,包括生成器和帶yield的生成器函數(shù)工腋。

這些可以直接作用于for循環(huán)的對象統(tǒng)稱為可迭代對象Iterable姨丈。

可以使用isinstance()判斷一個對象是否是Iterable對象

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

可以next()函數(shù)調(diào)用并不斷返回下一個值的對象稱為迭代器:Iterator

顯然可迭代對象迭代器是兩個不同的概念擅腰,它們的區(qū)別需要通過生成器加以理解蟋恬。

生成器

通過列表生成式,我們可以直接創(chuàng)建一個列表趁冈。但是歼争,受到內(nèi)存限制,列表容量肯定是有限的渗勘。而且沐绒,創(chuàng)建一個包含100萬個元素的列表,不僅占用很大的存儲空間旺坠,如果我們僅僅需要訪問前面幾個元素乔遮,那后面絕大多數(shù)元素占用的空間都白白浪費了。

因此取刃,Python實現(xiàn)了一種一邊循環(huán)一邊計算的機制蹋肮,稱為生成器:generator。相比于列表生成式直接生成數(shù)據(jù)璧疗,生成器存儲循環(huán)推演算法以及當前值坯辩。

創(chuàng)建生成器的一個簡單方法是把一個列表生成式的[]改成()

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

創(chuàng)建Lg的區(qū)別僅在于最外層的[]()崩侠,L是一個list漆魔,而g是一個generator。

g默認輸出對象信息啦膜,需要通過next()函數(shù)獲得generator的下一個返回值:

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

我們講過,generator保存的是算法淌喻,每次調(diào)用next(g)僧家,就可以通過當前的元素值計算出g的下一個元素的值,直到計算到最后一個元素裸删,沒有更多的元素時八拱,拋出StopIteration的錯誤

顯然這種操作不實用,根據(jù)上一節(jié)的介紹肌稻,生成器也是可迭代對象清蚀,因此可以使用for循環(huán)。

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
... 
0
1
4
9
16
25
36
49
64
81

所以爹谭,我們創(chuàng)建了一個generator后枷邪,基本上永遠不會調(diào)用next(),而是通過for循環(huán)來迭代它诺凡,并且不需要關(guān)心StopIteration的錯誤东揣。

生成器函數(shù)

generator非常強大。如果推算的算法比較復雜腹泌,用類似列表生成式的for循環(huán)無法實現(xiàn)的時候嘶卧,還可以用函數(shù)來實現(xiàn)。

比如凉袱,著名的斐波拉契數(shù)列(Fibonacci)芥吟,除第一個和第二個數(shù)外,任意一個數(shù)都可由前兩個數(shù)相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契數(shù)列用列表生成式寫不出來专甩,但是钟鸵,用函數(shù)把它打印出來卻很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

上面的函數(shù)可以輸出斐波那契數(shù)列的前N個數(shù):

>>> fib(6)
1
1
2
3
5
8
'done'

仔細觀察,可以看出配深,fib函數(shù)實際上是定義了斐波拉契數(shù)列的推算規(guī)則携添,可以從第一個元素開始,推算出后續(xù)任意的元素篓叶,這種邏輯其實非常類似generator烈掠。

也就是說,上面的函數(shù)和generator僅一步之遙缸托。要把fib函數(shù)變成generator左敌,只需要把print(b)改為yield b就可以了

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

這就是定義generator的另一種方法。如果一個函數(shù)定義中包含yield關(guān)鍵字俐镐,那么這個函數(shù)就不再是一個普通函數(shù)矫限,而是一個generator:

>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

這里,最難理解的就是generator和函數(shù)的執(zhí)行流程不一樣佩抹。函數(shù)是順序執(zhí)行叼风,遇到return語句或者最后一行函數(shù)語句就返回。而變成generator的函數(shù)棍苹,在每次調(diào)用next()的時候執(zhí)行无宿,遇到yield語句返回,再次執(zhí)行時從上次返回的yield語句處繼續(xù)執(zhí)行枢里。

舉個簡單的例子孽鸡,定義一個generator蹂午,依次返回數(shù)字1,3彬碱,5:

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)

調(diào)用該generator時豆胸,首先要生成一個generator對象,然后用next()函數(shù)不斷獲得下一個返回值:

>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

可以看到巷疼,odd不是普通函數(shù)晚胡,而是generator,在執(zhí)行過程中皮迟,遇到yield就中斷搬泥,下次又繼續(xù)執(zhí)行。執(zhí)行3次yield后伏尼,已經(jīng)沒有yield可以執(zhí)行了忿檩,所以,第4次調(diào)用next(o)就報錯爆阶。

但是用for循環(huán)調(diào)用generator時燥透,發(fā)現(xiàn)拿不到generator的return語句的返回值。如果想要拿到返回值辨图,必須捕獲StopIteration錯誤班套,返回值包含在StopIterationvalue中:

>>> g = fib(6)
>>> while True:
...     try:
...         x = next(g)
...         print('g:', x)
...     except StopIteration as e:
...         print('Generator return value:', e.value)
...         break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done 

相互關(guān)系

可以使用isinstance()判斷一個對象是否是Iterator對象:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator) # 這是一個生成器
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是Iterator對象,但list故河、dict吱韭、str雖然是Iterable,卻不是Iterator鱼的。

list理盆、dictstrIterable變成Iterator可以使用iter()函數(shù):

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

為什么list凑阶、dict猿规、str等數(shù)據(jù)類型不是Iterator

這是因為Python的Iterator對象表示的是一個數(shù)據(jù)流宙橱,Iterator對象可以被next()函數(shù)調(diào)用并不斷返回下一個數(shù)據(jù)姨俩,直到?jīng)]有數(shù)據(jù)時拋出StopIteration錯誤。可以把這個數(shù)據(jù)流看做是一個有序序列师郑,但我們卻不能提前知道序列的長度环葵,只能不斷通過next()函數(shù)實現(xiàn)按需計算下一個數(shù)據(jù),所以Iterator的計算是惰性的宝冕,只有在需要返回下一個數(shù)據(jù)時它才會計算张遭。

Iterator甚至可以表示一個無限大的數(shù)據(jù)流,例如全體自然數(shù)猬仁。而使用list是永遠不可能存儲全體自然數(shù)的帝璧。

小結(jié)

凡是可作用于for循環(huán)的對象都是可迭代類型;

凡是可作用于next()函數(shù)的對象都是迭代器類型湿刽,它們表示一個惰性計算的序列的烁;

集合數(shù)據(jù)類型如listdict诈闺、str等是Iterable但不是Iterator性宏,不過可以通過iter()函數(shù)獲得一個Iterator對象纯露。

Python的for循環(huán)本質(zhì)上就是通過不斷調(diào)用next()函數(shù)實現(xiàn)的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

實際上完全等價于:

# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環(huán):
while True:
    try:
        # 獲得下一個值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循環(huán)
        break
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市姿搜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌候齿,老刑警劉巖迄靠,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卓缰,居然都是意外死亡计呈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門征唬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捌显,“玉大人,你說我怎么就攤上這事总寒》鐾幔” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵摄闸,是天一觀的道長善镰。 經(jīng)常有香客問我,道長贪薪,這世上最難降的妖魔是什么媳禁? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮画切,結(jié)果婚禮上竣稽,老公的妹妹穿的比我還像新娘。我一直安慰自己霍弹,他們只是感情好毫别,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著典格,像睡著了一般岛宦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耍缴,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天砾肺,我揣著相機與錄音挽霉,去河邊找鬼。 笑死变汪,一個胖子當著我的面吹牛侠坎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播裙盾,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼实胸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了番官?” 一聲冷哼從身側(cè)響起庐完,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徘熔,沒想到半個月后门躯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡酷师,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年生音,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窒升。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡缀遍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饱须,到底是詐尸還是另有隱情域醇,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布蓉媳,位于F島的核電站譬挚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏酪呻。R本人自食惡果不足惜减宣,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玩荠。 院中可真熱鬧漆腌,春花似錦、人聲如沸阶冈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽女坑。三九已至填具,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匆骗,已是汗流浹背劳景。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工誉简, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盟广。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓描融,卻偏偏與公主長得像,于是被迫代替她去往敵國和親衡蚂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容

  • https://zhuanlan.zhihu.com/p/26123333 要完全理解透生成器骏庸,需要我們先掌握三個...
    Lauzanhing閱讀 409評論 0 0
  • 1.迭代器(Iterator) 可以直接作用于for循環(huán)的數(shù)據(jù)類型有以下幾種:一類是集合數(shù)據(jù)類型毛甲,如list、tu...
    Wayne_Dream閱讀 181評論 0 0
  • 迭代 循環(huán)中執(zhí)行一遍循環(huán)體且選擇下一個進入循環(huán)體的變量的過程 迭代器 迭代器是定義__next__方法的對象具被。(并...
    zlrs閱讀 1,820評論 2 0
  • 又到星期五玻募,這星期由于清明調(diào)休,有6天一姿,所以熬到這一天也甚是不易七咧。同學們?nèi)绱耍乙踩绱硕L荆∶客矶际且徽凑眍^就睡...
    這個昵稱可以閱讀 217評論 2 2
  • 終于還是明白我們最大的敵人應該是自以為是的朋友艾栋。你以為只要我們談笑風生,每天樂此不疲的來來去去蛉顽。我們就可以...
    毒蠱人閱讀 215評論 0 2