迭代器(Iterator)和可迭代對象(Iterable)每每是綁定的。可迭代對象就是咱們平時常常用的list
,string
, tuple
這種。事實上迭代器的概念會比可迭代對象寬泛不少,一會舉幾個例子就能明白。python
在使用list
這種數據類型的時候,咱們常常會使用下面這種迭代方式:express
# eg 1 mylist = [1,2,3,4] for x in mylist: print(x) >>>1 >>>2 >>>3 >>>4
有時候會很奇怪for循環爲何能夠這麼用,列表的索引居然會自動日後一個一個走,走到結尾了,還會自動停下來,不會有list out of range
的報錯。神奇。其實for循環作的事情不止這麼簡單。要說明這個問題,得先說迭代器具體怎麼作,是什麼。函數
要建立一個迭代器,就必需要實現兩個方法,分別是__iter__()
和__next__()
,以及實現異常機制StopIteration
。請看下面的例子:code
# eg 2 class PowTwo: """Class to implement an iterator of powers of two""" def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n <= self.max: result = 2 ** self.n self.n += 1 return result else: raise StopIteration
能夠看到,迭代器是經過寫一個類來定義的,類裏面實現了剛剛所說的__iter__()
和__next__()
方法。這個迭代器是用來產生一系列2的指數次方的數字的,具體用法看下面:對象
a = PowTwo(4) i = iter(a) # attention please next(i) >>>1 next(i) >>>2 next(i) >>>4 next(i) >>>8 next(i) >>>16 next(i) >>>Traceback (most recent call last): ... StopIteration
仔細看哦,第一行代碼用4建立了一個實例a
,設置了這個迭代器的迭代上限,而後並非直接用這個實例就能夠了,還得調用iter()
函數去把a
完全進化成一個Iterator
,進而有了接下來next()
函數的閃亮登場。其實我也蠻奇怪,直接用這個類很差麼,好比下面的代碼:索引
a = PowTwo(4) a.__iter__() a.__next__() >>>1 a.__next__() >>>2 a.__next__() >>>4 a.__next__() >>>8 a.__next__() >>>16 next(i) >>>Traceback (most recent call last): ... StopIteration
徹底沒問題,可是你本身比較一下兩者的代碼,哪一個美一點毋庸置疑。。。再說了,跟裝飾器同樣,一切爲了簡潔優美而生嘛。element
OK,能夠回到最初的起點——for循環了。示例1中的代碼,for循環到底作了什麼呢,答案是,for循環實際上是先把mylist
變成迭代器,而後用while循環去迭代:generator
iter_obj = iter(mylist) while True: try: element = next(iter_obj) except StopIteration: break
這樣一路解釋過來,應該就不難理解迭代器了。總結一下就是:string
iter()
函數去初始化它,而後瘋狂next()
便可;__iter__()
和__next__()
方法,以及異常機制StopIteration
,而後操做同1;StopIteration
便可。 這玩意兒就比迭代器複雜點了,因此還得分幾個小點,逐個擊破。it
生成器也是Python中面向一系列須要迭代的問題,經常使用的解決方案。既然有了迭代器,能夠解決不少迭代的問題,那爲啥子還要生成器勒?
主要的緣由是迭代器的開銷太大了。對於一些小問題還好,大的問題須要迭代的元素很龐大的時候,迭代器就使不上勁兒了。並且,建立一個迭代器,說實話也還挺麻煩的,看看上面的小總結的第二點,你得實現這些方法和手動處理異常。
並且迭代器要寫類,其實一個函數能夠搞定的事情,何須那麼複雜。正是應了這個景,生成器也就是在函數對象上搞事情的。
這個緣由點到即止,先把生成器講清楚了,天然就通透了。先看一個小例子,寫一個生成器函數(Generator Function):
# eg 1 def my_gen(): n = 1 print('This is printed first') yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
上面就是一個簡單的生成器函數,多了一種關鍵字yield
,細心看會發現這個函數居然沒有return
!再細看代碼,沒錯,yield
替代了return
。那怎麼用呢,有兩種用法以下:
# usage 1 a = my_gen() next(a) >>>This is printed first 1 next(a) >>>This is printed second 2 next(a) >>>This is printed at last 3 next(a) >>>Traceback (most recent call last): ... StopIteration
# usage 2 for item in my_gen(): print(item) >>> This is printed first 1 This is printed second 2 This is printed at last 3
對於用法1,把函數賦值給了a
,然乎瘋狂next()
便可。你會發現,咱們並無像迭代器那樣實現__iter__()
和__next__()
方法,以及異常機制StopIteration
,只是用了一個yield
關鍵字,這個生成器函數卻達到了迭代器同樣的效果。
對於用法2,更牛皮了,甚至不用賦值的操做,直接for這個生成器函數。。。
剛剛那個小函數,就是一個最普通的例子,那問題是若是有多個n想要玩,豈不是來多少手動寫多少?那固然還有循環的玩法,帶有循環機制的生成器。下面是一個逆序輸出的小例子:
# eg 2 def rev_str(my_str): length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i] for char in rev_str("hello"): print(char) >>>o >>>l >>>l >>>e >>>h
沒錯,真無聊,犯得上逆序輸出的程序還得上生成器麼,犯不着,可是,只是想給出這個循環機制生成器的概念。若是你發現這個rev_str()
函數和普通的逆序函數徹底同樣,只是return
換成了yield
,那就萬事大吉,要理解的就是這個。就這樣一記簡單的替換操做,你就獲得了一個生成器,是否是比迭代器省事兒多了。
不知道你有沒有用過列表生成式,沒用過也應該看到過,這相似於匿名函數,語法簡潔,好比:
# eg 3 my_list = [1, 3, 6, 10] [x**2 for x in my_list] >>>[1, 9, 36, 100]
生成器表達式和這個幾乎同樣,不信你看:
# eg 4 my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) next(a) >>>1 next(a) >>>9 next(a) >>>36 next(a) >>>100
把列表生成式的[]
直接改爲()
,就獲得了一個生成器。
回到最開始迭代器的那個類的例子,用生成器咋寫呢?
def PowTwoGen(max = 0): n = 0 while n < max: yield 2 ** n n += 1
簡潔吧,而後你就能夠用這個生成器函數遨遊了。
一樣的一個須要迭代的功能,若是用普通函數寫,一旦須要迭代的元素特別多,在使用的時候,普通函數須要等全部的元素計算出來了,而後把返回值給你。生成器就不是了,它一次計算出一個,用的時候就取一個,而且它還會記住位置,下次用就計算下一個,這樣對空間的開銷也是很小的。
看下面的函數:
def all_even(): n = 0 while True: yield n n += 2
寫普通函數,你必然作不到寫出一個能夠無限操做的函數,生成器卻能夠。(迭代器也能夠,就是麻煩點兒)
# 利用yield生成一個斐波那契數列的生成器 def fib(max): n,a,b=0,0,1 while n<max: yield b a,b=b,a+b n+=1 return 'done' # 要不要這句話都行 f=fib(6) next(f) # 瘋狂next()它 >>> 1 1 2 3 5 8 Traceback (most recent call last): ... StopIteration: done