來源:本人博客javascript
迭代器和生成器可能對於一些人來講知道是什麼東東,可是並無比較深刻的瞭解,那麼今天,就跟隨我來了解一下這二者的概念,關係及優勢,我將使用python中的迭代器和生成器做爲演示,若是你不懂python不要緊,明白了概念,剩下的就只是編程語言的差別了!這一點很關鍵,再囉嗦一句,不要爲了編程而編程,也要明白一些概念性的東西,編程語言只是工具!html
想必你們在學習編程的時候,確定學到過for循環,while循環,do...while循環等等,那麼咱們爲何須要循環操做呢?由於有些時候咱們但願計算機爲咱們重複的執行一樣的操做,好比我有一個「數組」,裏面存儲了100個同窗的id,那麼我則會對這個數組進行循環操做,而後挨個輸出。固然還有不少其餘地方須要循環操做,這裏我只是舉個例子。java
因此,循環操做是計算機編程語言中必不可少的組成部分,那麼請你們用幾秒鐘時間回想一下,咱們以前曾經寫過的循環操做for循環,while循環。咱們每每須要初始化一個變量i,還得聲明一個條件好比i<100,而後循環完每一步以後作什麼,好比(下方僞代碼):python
for(i = 0; i < 100; i++) { }
咱們能夠很容易的用這種循環來遍歷一個數組,但願你們學過數據結構,由於數組在內存中的存儲是連續的!咱們能夠經過數組的「下標」(實際上是相對於數組第一個元素的位置)來進行訪問數組中的元素,因此在不少時候,咱們經過for循環來遍歷數組(下方僞代碼):編程
for(i = 0; i < arrLength; i++) { }
那麼若是我如今問你,你怎麼進行遍歷一個沒有在內存中連續存儲的「數據結構」呢,好比python中的「字典」,javascript中的」對象「,又好比你本身寫了一個」樹「結構的類,想遍歷整個樹的節點?那麼傳統的for循環,while循環就沒法發揮他們的做用了,這個時候咱們就應該引入」迭代器「了。數組
因此,小結一下,」迭代器「其實目的也是爲了」循環「,更嚴謹一些,是爲了「遍歷」,你能夠把迭代器當作比普通循環更高級別的工具,普通循環能搞定的迭代器也能搞定,普通循環搞不定的迭代器還能搞定,而且使用迭代器比普通循環效率更高,這個咱們後面說到生成器的時候會提到。數據結構
我想大多數人可能和我同樣,剛開始對這些概念/名詞都很模糊,那麼讓咱們一塊兒弄明白他們。app
你們先要知道「協議」(protocol)的意思,其實協議是用來「規範/標準化」你「創造的東西」的。好比,你開天闢地的發明了一種東西叫作「吧啦嗶哩」,你給小明說:「小明,給我發一個吧啦嗶哩過來」,若是小明不知道啥叫「吧啦嗶哩」,那麼小明會直接懵逼的。這時候你就要定一個「協議」以下:編程語言
1, "吧啦嗶哩"一共有10個字
2, "吧啦嗶哩"開頭和結尾都是"#"號 (佔兩個字)
3, "吧啦嗶哩"最後四位是"blbl"
4, 其餘隨便函數
那麼咱們根據這個協議,能夠很輕易的構造出「吧啦嗶哩」來:#1234blbl# 或者 #8888blbl#
一樣,咱們根據這份協議,就能夠用來檢測你獲得的是否是「吧啦嗶哩」,#1234blbl# -> 是,#1234blbl!-> 不是
明白了上面的東西,下面咱們就開始「迭代」之旅,迭代顧名思義,就是重複的的既定的任務,直到完成。因此,爲了完成迭代,咱們須要一個迭代器!那麼什麼是迭代器呢?來看看迭代器的協議吧
從前有我的發明了迭代器,爲了讓你們明白什麼是迭代器,他就寫了這個協議,那麼協議的內容簡而言之就是一句話:若是一個對象包括一個叫"next"(python3 爲__next__)的方法,那麼這個對象就叫作「迭代器」。
好了,那麼咱們根據這個協議能夠建立一個迭代器(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),讓這個語法糖來替咱們反覆執行__next__方法,好比python中的"for.. in",可是,爲了讓這個反覆執行的過程停下來,咱們一樣須要定義一個終止信號,在python中,終止信號就是拋出一個StopIteration的「例外」(exception),來告知咱們的語法糖:」好啦,沒東西能夠迭代了,能夠停了「,這樣迭代就終止了。
因此咱們再進一步規範一下咱們建立的迭代器成以下形式:
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例外的時候中止罷了。
可是當你使用別人(編程語言)實現編寫好的語法糖時,你就必須按照他們的規則走。
好了,咱們如今明白了,一般來說,當咱們要建立了一個迭代器時,咱們還「必須」(注意是必須)讓迭代器可迭代,這樣理解:由於一個不可迭代的迭代器是沒有意義的!
因此,注意!從如今開始到文章結束,我所說的「迭代器」都是「可迭代」的迭代器!
那麼怎麼讓個人迭代器可迭代呢?一樣,來看什麼是「可迭代協議」(iterable protocol)
在python中,爲了使一個」對象「可迭代:
1,這個迭代器必須同時包含另外一個方法叫作「__iter__」
2,這個"__iter__"方法還得返回一個」迭代器「(可迭代)
請注意,上面我說的是:爲了使一個」對象「可迭代,這裏,對象能夠指咱們剛剛建立的」Counter「迭代器,也能夠是其餘的對象。
來個栗子:
爲了使咱們剛纔建立的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的迭代!
這裏簡單說一些執行步驟,當咱們使用for..in語法糖的時候,它先調用__iter__方法,獲得返回的迭代器,而後連續調用該迭代器的__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
如今邏輯不是很複雜的狀況之下,這種建立迭代器的方式仍是可以接受的,可是若是邏輯複雜,以及用這種模式多了,每次這麼定義就不是很方便,因而爲了「簡化」建立迭代器的過程,「生成器」generator就出現了。
生成器的出現,就是爲了簡化建立迭代器的繁雜,同時又要保證邏輯的清晰,說到底生成器就是爲了更方便咱們使用迭代器而生的,生成器的特性以下:
1, 生成器的樣子就是一個普通的函數,只不過return關鍵詞被yield取代了
2, 當調用這個「函數」的時候,它會當即返回一個迭代器,而不當即執行函數內容,直到調用其返回迭代器的next方法是纔開始執行,直到遇到yield語句暫停。
3, 繼續調用生成器返回的迭代器的next方法,恢復函數執行,直到再次遇到yield語句
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以前全部的整數,最後對這些整數進行求和。
這個代碼使用了咱們傳統的while循環,若是接受的參數n比較小還好,可是當接受的參數很大時,對內存的消耗就凸顯出來了,由於在執行該函數的過程當中,
nums這個大的列表會所有存在於內存中。而且求和運算只有當nums列表徹底構建完成以後才能夠進行運算,效率也高。
而用迭代器(生成器)的方法則會大大提升效率,一方面每次next循環都會yield出一個值,供sum函數累加使用,這樣就不用佔用很大的內存,另外一方面,使用迭代器/生成器也不用徹底等到前n個數所有遍歷完再進行累加,效率更高!