迭代器(ITERATOR)和生成器(GENERATOR)

前言


迭代器和生成器可能對于一些人來說知道是什么東東旅挤,但是并沒有比較深入的了解切威,那么今天伍宦,就跟隨我來了解一下這兩者的概念砚殿,關系及優(yōu)點啃憎。我將使用python中的迭代器和生成器作為演示,如果你不懂python沒關系似炎,明白了概念辛萍,剩下的就只是編程語言的差異了悯姊!這一點很關鍵,再啰嗦一句贩毕,不要為了編程而編程悯许,也要明白一些概念性的東西,編程語言只是工具辉阶!

從循環(huán)開始說起


想必大家在學習編程的時候先壕,肯定學到過for循環(huán),while循環(huán)谆甜,do...while循環(huán)等等垃僚,那么我們?yōu)槭裁葱枰h(huán)操作呢?因為有些時候我們希望計算機為我們重復的執(zhí)行同樣的操作规辱,比如我有一個“數組”谆棺,里面存儲了100個同學的id,那么我則會對這個數組進行循環(huán)操作罕袋,然后挨個輸出改淑。當然還有很多其他地方需要循環(huán)操作,這里我只是舉個例子浴讯。

所以朵夏,循環(huán)操作是計算機編程語言中必不可少的組成部分,那么請大家用幾秒鐘時間回想一下兰珍,我們之前曾經寫過的循環(huán)操作for循環(huán)侍郭,while循環(huán)。我們往往需要初始化一個變量i掠河,還得聲明一個條件比如i<100亮元,然后循環(huán)完每一步之后做什么,比如(下方偽代碼):

for(i = 0; i < 100; i++) {
    
}

我們可以很容易的用這種循環(huán)來遍歷一個數組唠摹,希望大家學過數據結構爆捞,因為數組在內存中的存儲是連續(xù)的!我們可以通過數組的“下標”(其實是相對于數組第一個元素的位置)來進行訪問數組中的元素勾拉,所以在很多時候煮甥,我們通過for循環(huán)來遍歷數組(下方偽代碼):

for(i = 0; i < arrLength; i++) {
    
}

那么如果我現(xiàn)在問你,你怎么進行遍歷一個沒有在內存中連續(xù)存儲的“數據結構”呢藕赞,比如python中的“字典”成肘,javascript中的”對象“,又比如你自己寫了一個”樹“結構的類斧蜕,想遍歷整個樹的節(jié)點双霍?那么傳統(tǒng)的for循環(huán),while循環(huán)就無法發(fā)揮他們的作用了,這個時候我們就應該引入”迭代器“了洒闸。

所以染坯,”迭代器“其實目的也是為了”循環(huán)“,更嚴謹一些丘逸,是為了“遍歷”单鹿,你可以把迭代器看成比普通循環(huán)更高級別的工具,普通循環(huán)能搞定的迭代器也能搞定深纲,普通循環(huán)搞不定的迭代器還能搞定仲锄,并且使用迭代器比普通循環(huán)效率更高,這個我們后面說到生成器的時候會提到囤萤。

迭代(iteration)/可迭代(iterable)/迭代器(iterator)


我想大多數人可能和我一樣昼窗,剛開始對這些概念/名詞都很模糊是趴,那么讓我們一起弄明白他們涛舍。

大家先要知道“協(xié)議”(protocol)的意思,其實協(xié)議是用來“規(guī)范/標準化”你“創(chuàng)造的東西”的唆途。比如富雅,你開天辟地的發(fā)明了一種東西叫做“吧啦嗶哩”,你給小明說:“小明肛搬,給我發(fā)一個吧啦嗶哩過來”没佑,如果小明不知道啥叫“吧啦嗶哩”,那么小明會直接懵逼的温赔。這時候你就要定一個“協(xié)議”如下:

  1. "吧啦嗶哩"一共有10個字
  2. "吧啦嗶哩"開頭和結尾都是"#"號 (占兩個字)
  3. "吧啦嗶哩"最后四位是"blbl"
  4. 其他隨便

那么我們根據這個協(xié)議蛤奢,可以很輕易的構造出“吧啦嗶哩”來:#1234blbl# 或者 #8888blbl#

同樣,我們根據這份協(xié)議陶贼,就可以用來檢測你得到的是不是“吧啦嗶哩”啤贩,#1234blbl# -> 是,#1234blbl拜秧!-> 不是

迭代(iteration)

明白了上面的東西痹屹,下面我們就開始“迭代”之旅,迭代顧名思義枉氮,就是重復的的既定的任務志衍,直到完成。所以聊替,為了完成迭代楼肪,我們需要一個迭代器!那么什么是迭代器呢惹悄?來看看迭代器的協(xié)議吧

迭代器協(xié)議 iterator protocol

從前有個人發(fā)明了迭代器春叫,為了讓大家明白什么是迭代器,他就寫了這個協(xié)議,那么協(xié)議的內容簡而言之就是一句話:如果一個對象包括一個叫"next"(python3 為__next__)的方法象缀,那么這個對象就叫做“迭代器”蔬将。

好了,那么我們根據這個協(xié)議可以創(chuàng)建一個迭代器(iterator)

class Counter:
    def __init__(self):
        self.index = 0

    def __next__(self):
        i = self.index
        if i < 10:
            self.index += 1
            return i

這個Counter就是一個迭代器央星,但是目前它沒有什么太大的作用霞怀,因為我們不可能每次通過手動調用__next__方法來進行操作。

好消息是莉给,很多編程軟件為我們提供了一個“語法糖”(syntactic sugar)毙石,讓這個語法糖來替我們反復執(zhí)行__next__方法,比如python中的"for.. in"颓遏,但是徐矩,為了讓這個反復執(zhí)行的過程停下來,我們同樣需要定義一個終止信號叁幢,在python中滤灯,終止信號就是拋出一個StopIteration的“例外”(exception),來告知我們的語法糖:”好啦曼玩,沒東西可以迭代了鳞骤,可以停了“,這樣迭代就終止了黍判。

所以我們再進一步規(guī)范一下我們創(chuàng)建的迭代器成如下形式:

class Counter:
    def __init__(self):
        self.index = 0

    def __next__(self):
        i = self.index
        if i < 10:
            self.index += 1
            return i
        else:
            raise StopIteration

好了豫尽,我們來試一下:

counter = Counter()

for i in counter:
    print(i)

不妙,報錯了顷帖。美旧。

TypeError: 'Counter' object is not iterable

錯誤顯示說:這個Counter對象不是可迭代的!這是什么意思呢贬墩?

原來榴嗅,為了使用這個for..in 迭代語法糖,我們需要在in后面放可以迭代的“迭代器”震糖,什么是可以迭代录肯?你可以認為就是可以使用for..in語法糖,讓語法糖幫你重復調用next方法就好了吊说。如果不可以迭代论咏,那么for..in這個語法糖就無法為我們自動調用next方法。

所以說颁井,為了使用for..in語法糖來進行迭代我們的迭代器厅贪,你必須讓你的迭代器可迭代(有點繞。雅宾。哈哈)养涮。

這句話有兩層含義:

  1. 為了使用for..in語法糖,你必須讓你的迭代器可迭代
  2. 你如果不適用for..in語法糖,你就不必讓你的迭代器可迭代贯吓,你可以自己寫一個語法糖懈凹,不斷地調用next方法,當遇到StopIteration例外的時候停止罷了悄谐。

好了介评,我們現(xiàn)在明白了,通常來講爬舰,當我們要創(chuàng)建了一個迭代器時们陆,我們還“必須”(注意是必須)讓迭代器可迭代,這樣理解:因為一個不可迭代的迭代器是沒有意義的情屹!

所以坪仇,注意!從現(xiàn)在開始到文章結束垃你,我所說的“迭代器”都是“可迭代”的迭代器椅文!

那么怎么讓我的迭代器可迭代呢?同樣蜡镶,來看什么是“可迭代協(xié)議”(iterable protocol)

可迭代協(xié)議 iterable protocol

在python中雾袱,為了使一個”對象“可迭代:

  1. 這個迭代器必須同時包含另一個方法叫做“__iter__”
  2. 這個"__iter__"方法還得返回一個”迭代器“(可迭代)

請注意恤筛,上面我說的是:為了使一個”對象“可迭代官还,這里,對象可以指我們剛剛創(chuàng)建的”Counter“迭代器毒坛,也可以是其他的對象望伦。

來個栗子:
為了使我們剛才創(chuàng)建的Counter迭代器對象“可迭代”,那么:

  1. 我們就在這個Counter對象里面添加一個叫__iter__的方法 (可迭代化操作)
  2. 讓這個__iter__方法返回一個“可迭代的迭代器” (這里就是自己了煎殷!)
class Counter:
    def __init__(self):
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        i = self.index
        if i < 10:
            self.index += 1
            return i
        else:
            raise StopIteration

counter = Counter()
for i in counter:
    print(i)

Cool! 這個時候我們得到了0屯伞,1,2豪直,3劣摇,4,5弓乙,6末融,7,8暇韧,9的迭代勾习!

這里簡單說一些執(zhí)行步驟,當我們使用for..in語法糖的時候懈玻,它先調用__iter__方法巧婶,得到返回的迭代器,然后連續(xù)調用該迭代器的__next__方法,知道遇到StopIteration例外艺栈。

我上面也提到了英岭,我們不僅可以使迭代器“可迭代”,我們也可以使普通的對象“可迭代”湿右,只需給該對象添加一個__iter__的方法巴席,然后返回一個可迭代的迭代器就好了!

這里順便插一句诅需!在python中漾唉,我們可以使用"iter"這個函數來返回一個“可迭代的迭代器”。

比如:

x = iter([1, 2, 3])
print(x) #<list_iterator object at 0x10c828550>
x.__next__() # 返回 1
x.__next__() # 返回 2
x.__next__() # 返回 3
x.__next__() # 返回 StopIteration

所以堰塌,我們可以讓一個普通對象可迭代赵刑,而不一定非得是迭代器。

class Name:
    def __iter__(self):
        return iter(['zhangsan', 'lisi', 'wangwu'])

name = Name()
for n in name:
    print(n)

不錯场刑!我們得到了zhangsan, lisi, wangwu

現(xiàn)在邏輯不是很復雜的情況之下般此,這種創(chuàng)建迭代器的方式還是能夠接受的,但是如果邏輯復雜牵现,以及用這種模式多了铐懊,每次這么定義就不是很方便,于是為了“簡化”創(chuàng)建迭代器的過程瞎疼,“生成器”generator就出現(xiàn)了科乎。

生成器generator


生成器的出現(xiàn),就是為了簡化創(chuàng)建迭代器的繁雜贼急,同時又要保證邏輯的清晰茅茂,說到底生成器就是為了更方便我們使用迭代器而生的,生成器的特性如下:

  1. 生成器的樣子就是一個普通的函數太抓,只不過return關鍵詞被yield取代了
  2. 當調用這個“函數”的時候空闲,它會立即返回一個迭代器,而不立即執(zhí)行函數內容走敌,直到調用其返回迭代器的next方法是才開始執(zhí)行碴倾,直到遇到y(tǒng)ield語句暫停。
  3. 繼續(xù)調用生成器返回的迭代器的next方法掉丽,恢復函數執(zhí)行跌榔,直到再次遇到y(tǒng)ield語句
  4. 如此反復,一直到遇到StopIteration

看如下例子:

def gFun():
    print('before hello')
    yield 'hello'
    print('after hello')

a = gFun() # 調用生成器函數机打,返回一個迭代器并賦給a

print(a) # <generator object gFun at 0x104cd2a40> 得到一個生成器對象(迭代器)
print(a.__next__())
# before hello
# hello
print(a.__next__())
# after hello
# StopIteration

同時因為調用生成器函數返回的是一個迭代器矫户,所以我們可以使用for..in語法糖對其進行迭代操作:

a = gFun()
for x in a:
    print(x)

迭代返回了before hello, hello, after hello

使用迭代器/生成器的好處


首先快速看一段代碼:

def firstn(n):
    num, nums = 0, []
    while num < n:
        nums.append(num)
        num += 1
        return nums

sum_of_first_n = sum(firstn(1000000))

這段代碼定一個了一個函數firstn,該函數接受一個參數n残邀,返回n之前所有的整數皆辽,最后對這些整數進行求和柑蛇。

這個代碼使用了我們傳統(tǒng)的while循環(huán),如果接受的參數n比較小還好驱闷,但是當接受的參數很大時耻台,對內存的消耗就凸顯出來了,因為在執(zhí)行該函數的過程中空另,nums這個大的列表會全部存在于內存中盆耽。并且求和運算只有當nums列表完全構建完成之后才可以進行運算,效率也高扼菠。

而用迭代器(生成器)的方法則會大大提高效率摄杂,一方面每次next循環(huán)都會yield出一個值,供sum函數累加使用循榆,這樣就不用占用很大的內存析恢,另一方面,使用迭代器/生成器也不用完全等到前n個數全部遍歷完再進行累加秧饮,效率更高映挂!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市盗尸,隨后出現(xiàn)的幾起案子柑船,更是在濱河造成了極大的恐慌,老刑警劉巖泼各,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞍时,死亡現(xiàn)場離奇詭異,居然都是意外死亡历恐,警方通過查閱死者的電腦和手機寸癌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弱贼,“玉大人,你說我怎么就攤上這事磷蛹∷甭茫” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵味咳,是天一觀的道長庇勃。 經常有香客問我,道長槽驶,這世上最難降的妖魔是什么责嚷? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮掂铐,結果婚禮上罕拂,老公的妹妹穿的比我還像新娘揍异。我一直安慰自己,他們只是感情好爆班,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布衷掷。 她就那樣靜靜地躺著,像睡著了一般柿菩。 火紅的嫁衣襯著肌膚如雪戚嗅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天枢舶,我揣著相機與錄音懦胞,去河邊找鬼。 笑死凉泄,一個胖子當著我的面吹牛医瘫,可吹牛的內容都是我干的。 我是一名探鬼主播旧困,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼醇份,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吼具?” 一聲冷哼從身側響起僚纷,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拗盒,沒想到半個月后怖竭,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡陡蝇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年痊臭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片登夫。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡广匙,死狀恐怖,靈堂內的尸體忽然破棺而出恼策,到底是詐尸還是另有隱情鸦致,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布涣楷,位于F島的核電站分唾,受9級特大地震影響,放射性物質發(fā)生泄漏狮斗。R本人自食惡果不足惜绽乔,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碳褒。 院中可真熱鬧折砸,春花似錦看疗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至睹逃,卻和暖如春盗扇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沉填。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工疗隶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翼闹。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓斑鼻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猎荠。 傳聞我的和親對象是個殘疾皇子坚弱,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容