python迭代器与生成器_Python的迭代器和生成器
一 概要
在了解Python的數(shù)據(jù)結構時,容器(container)、可迭代對象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推導式(list,set,dict comprehension)眾多概念參雜在一起,難免讓初學者一頭霧水,我將用一篇文章試圖將這些概念以及它們之間的關系捋清楚
二 容器(container)
容器是一種把多個元素組織在一起的數(shù)據(jù)結構,容器中的元素可以逐個地迭代獲取,可以用 in , not in 關鍵字判斷元素是否包含在容器中。通常這類數(shù)據(jù)結構把所有的元素存儲在內(nèi)存中(也有一些特列并不是所有的元素都放在內(nèi)存)在Python中,常見的容器對象有:
list, deque, ....
set, frozensets, ....
dict, defaultdict, OrderedDict, Counter, ....
tuple, namedtuple, …
str
容器比較容易理解,因為你就可以把它看作是一個盒子、一棟房子、一個柜子,里面可以塞任何東西。從技術角度來說,當它可以用來詢問某個元素是否包含在其中時,那么這個對象就可以認為是一個容器,比如 list,set,tuples都是容器對象:
盡管絕大多數(shù)容器都提供了某種方式來獲取其中的每一個元素,但這并不是容器本身提供的能力,而是 可迭代對象 賦予了容器這種能力,當然并不是所有的容器都是可迭代的。
三 可迭代對象(iterable)
如果給定一個list或tuple,我們可以通過for循環(huán)來遍歷這個list或tuple,這種遍歷我們稱為迭代(Iteration)。
剛才說過,很多容器都是可迭代對象,此外還有更多的對象同樣也是可迭代對象,比如處于打開狀態(tài)的files,sockets等等。但凡是可以返回一個迭代器的對象都可稱之為可迭代對象,聽起來可能有點困惑,沒關系,可迭代對象與迭代器有一個非常重要的區(qū)別。先看一個例子:
這里 x 是一個可迭代對象,可迭代對象和容器一樣是一種通俗的叫法,并不是指某種具體的數(shù)據(jù)類型,list是可迭代對象,dict是可迭代對象,set也是可迭代對象。 y 和 z 是兩個獨立的迭代器,迭代器內(nèi)部持有一個狀態(tài),該狀態(tài)用于記錄當前迭代所在的位置,以方便下次迭代的時候獲取正確的元素。迭代器有一種具體的迭代器類型,比如 list_iterator , set_iterator 。可迭代對象實現(xiàn)了 __iter__ 和 __next__ 方法(python2中是 next 方法,python3是 __next__ 方法),這兩個方法對應內(nèi)置函數(shù) iter() 和 next() 。 __iter__ 方法返回可迭代對象本身,這使得他既是一個可迭代對象同時也是一個迭代器。
四 迭代器(iterator)
那么什么迭代器呢?它是一個帶狀態(tài)的對象,他能在你調(diào)用 next() 方法的時候返回容器中的下一個值,任何實現(xiàn)了 __next__() (python2中實現(xiàn) next() )方法的對象都是迭代器,至于它是如何實現(xiàn)的這并不重要。
現(xiàn)在我們就以斐波那契數(shù)列()為例,學習為何創(chuàng)建以及如何創(chuàng)建一個迭代器:
著名的斐波拉契數(shù)列(Fibonacci),除第一個和第二個數(shù)外,任意一個數(shù)都可由前兩個數(shù)相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
code1:
deffab(max):
n, a, b= 0, 0, 1
while n
a, b= b, a +b
n= n + 1
直接在函數(shù)fab(max)中用print打印會導致函數(shù)的可復用性變差,因為fab返回None。其他函數(shù)無法獲得fab函數(shù)返回的數(shù)列。
code2:
deffab(max):
L=[]
n, a, b= 0, 0, 1
while n
L.append(b)
a, b= b, a +b
n= n + 1
return L
代碼2滿足了可復用性的需求,但是占用了內(nèi)存空間,最好不要。
對比for i in range(1000): pass和for i in xrange(1000): pass,前一個返回1000個元素的列表,而后一個在每次迭代中返回一個元素,因此可以使用迭代器來解決復用可占空間的問題
code3:
classFab(object):def __init__(self, max):
self.max=max
self.n, self.a, self.b= 0, 0, 1
def __iter__(self):returnselfdefnext(self):if self.n
r=self.b
self.a, self.b= self.b, self.a +self.b
self.n= self.n + 1
returnrraiseStopIteration()'''>>> for key in Fabs(5):
print key
1
1
2
3
5'''
Fabs 類通過 next() 不斷返回數(shù)列的下一個數(shù),內(nèi)存占用始終為常數(shù)
Fib既是一個可迭代對象(因為它實現(xiàn)了 __iter__ 方法),又是一個迭代器(因為實現(xiàn)了 __next__ 方法)。實例變量 self .a 和 self.b 用戶維護迭代器內(nèi)部的狀態(tài)。每次調(diào)用 next() 方法的時候做兩件事:
為下一次調(diào)用 next() 方法修改狀態(tài)
為當前這次調(diào)用生成返回結果
迭代器就像一個懶加載的工廠,等到有人需要的時候才給它生成值返回,沒調(diào)用的時候就處于休眠狀態(tài)等待下一次調(diào)用。
五 for i in (iterable)的內(nèi)部實現(xiàn)
在大多數(shù)情況下,我們不會一次次調(diào)用next方法去取值,而是通過 for i in (iterable)的方式
注意:in后面的對象如果是一個迭代器,內(nèi)部因為有iter方法才可以進行操作,所以,迭代器協(xié)議里面有iter和next兩個方法,否則for語句無法應用。
注意:定時垃圾回收機制
for i in range(10):print i :定時垃圾回收機制:沒有引用指向這個對象,則被回收
六 生成器(generator)
生成器算得上是Python語言中最吸引人的特性之一,生成器其實是一種特殊的迭代器,不過這種迭代器更加優(yōu)雅。代碼3遠沒有代碼1簡潔,生成器(yield)既可以保持代碼1的簡潔性,又可以保持代碼3的效果。它不需要再像上面的類一樣寫 __iter__() 和 __next__() 方法了,只需要一個 yiled 關鍵字。 生成器有如下特征是它一定也是迭代器(反之不成立),因此任何生成器也是以一種懶加載的模式生成值。用生成器來實現(xiàn)斐波那契數(shù)列的例子是:
deffab(max):
n, a, b= 0, 0, 1
while n
a, b= b, a +b
n= n + 1
>>> for n in fab(5):printn1
1
2
3
5
fib 就是一個普通的python函數(shù),它特需的地方在于函數(shù)體中沒有 return 關鍵字,函數(shù)的返回值是一個生成器對象。當執(zhí)行 f=fib(5) 返回的是一個生成器對象,此時函數(shù)體中的代碼并不會執(zhí)行,只有顯示或隱示地調(diào)用next的時候才會真正執(zhí)行里面的代碼。
yield 的作用就是把一個函數(shù)變成一個 generator,帶有 yield 的函數(shù)不再是一個普通函數(shù),Python 解釋器會將其視為一個 generator,在 for 循環(huán)執(zhí)行時,每次循環(huán)都會執(zhí)行 fab 函數(shù)內(nèi)部的代碼,執(zhí)行到 yield b 時,fab 函數(shù)就返回一個迭代值,下次迭代時,代碼從 yield b 的下一條語句繼續(xù)執(zhí)行,而函數(shù)的本地變量看起來和上次中斷執(zhí)行前是完全一樣的,于是函數(shù)繼續(xù)執(zhí)行,直到再次遇到 yield。看起來就好像一個函數(shù)在正常執(zhí)行的過程中被 yield 中斷了數(shù)次,每次中斷都會通過 yield 返回當前的迭代值。
也可以手動調(diào)用 fab(5) 的 next() 方法(因為 fab(5) 是一個 generator 對象,該對象具有 next() 方法),這樣我們就可以更清楚地看到 fab 的執(zhí)行流程:
>>> f = fab(3)>>> f.__next__()1
>>> f.__next__()1
>>> f.__next__()2
>>> f.__next__()
Traceback (most recent call last):
File"", line 1, in f.next()
StopIteration
需要明確的就是生成器也是iterator迭代器,因為它遵循了迭代器協(xié)議。
兩種創(chuàng)建方式
包含yield的函數(shù)
生成器函數(shù)跟普通函數(shù)只有一點不一樣,就是把 return 換成yield,其中yield是一個語法糖,內(nèi)部實現(xiàn)了迭代器協(xié)議,同時保持狀態(tài)可以掛起。如下:
在一個生成器中,如果沒有return,則默認執(zhí)行到函數(shù)完畢;如果遇到return,如果在執(zhí)行過程中 return,則直接拋出 StopIteration 終止迭代。
復制代碼deff():yield 5
print("ooo")return
yield 6
print("ppp")#if str(tem)=='None':
#print("ok")
f=f()#print(f.__next__())#print(f.__next__())
for i inf:print(i)'''return即迭代結束
for不報錯的原因是內(nèi)部處理了迭代結束的這種情況'''
注意:
文件讀取
defread_file(fpath):
BLOCK_SIZE= 1024with open(fpath,'rb') as f:whileTrue:
block=f.read(BLOCK_SIZE)ifblock:yieldblockelse:return
如果直接對文件對象調(diào)用 read() 方法,會導致不可預測的內(nèi)存占用。好的方法是利用固定長度的緩沖區(qū)來不斷讀取文件內(nèi)容。通過 yield,我們不再需要編寫讀文件的迭代類,就可以輕松實現(xiàn)文件讀取。
生成器對象就是一種特殊的迭代器對象,滿足迭代器協(xié)議,可以調(diào)用next;對生成器對象for 循環(huán)時,調(diào)用iter方法返回了生成器對象,然后再不斷next迭代,而iter和next都是在yield內(nèi)部實現(xiàn)的。
例1:
defadd(s, x):return s +xdefgen():for i in range(4):yieldi
base=gen()for n in [1, 10]:
base= (add(i, n) for i inbase)print list(base)
'''核心語句就是:
for n in [1, 10]:
base = (add(i, n) for i in base)#在這里,base依舊是個生成器
在執(zhí)行l(wèi)ist(base)的時候,開始檢索,然后生成器開始運算了。關鍵是,這個循環(huán)次數(shù)是2,也就是說,有兩次生成器表達
式的過程。必須牢牢把握住這一點。
生成器返回去開始運算,n = 10而不是1沒問題吧,這個在上面提到的文章中已經(jīng)提到了,就是add(i, n)綁定的是n這個
變量,而不是它當時的數(shù)值。
然后首先是第一次生成器表達式的執(zhí)行過程:base = (10 + 0, 10 + 1, 10 + 2, 10 +3),這是第一次循環(huán)的結
果(形象表示,其實已經(jīng)計算出來了(10,11,12,3)),然后第二次,
base = (10 + 10, 11 + 10, 12 + 10, 13 + 10) ,終于得到結果了[20, 21, 22, 23].'''
例2:自定義range
七 生成器的擴展
生成器對象支持幾個方法,如gen.next() ,gen.send() ,gen.throw()等。
由于沒有額外的yield,所以將直接拋出StopIteration。
send的工作方式:
deff():print("ok")
s=yield 7
print(s)yield 8f=f()print(f.send(None))print(next(f))#print(f.send(None))等同于print(next(f)),執(zhí)行流程:打印ok,yield7,當再next進來時:將None賦值給s,然后返回8,可以通過斷點來觀察
協(xié)程應用:
所謂協(xié)同程序也就是是可以掛起,恢復,有多個進入點。其實說白了,也就是說多個函數(shù)可以同時進行,可以相互之間發(fā)送消息等。
importqueuedeftt():for x in range(4):print ('tt'+str(x) )yield
defgg():for x in range(4):print ('xx'+str(x) )yield
classTask():def __init__(self):
self._queue=queue.Queue()defadd(self,gen):
self._queue.put(gen)defrun(self):while notself._queue.empty():for i inrange(self._queue.qsize()):try:
gen=self._queue.get()
gen.send(None)exceptStopIteration:pass
else:
self._queue.put(gen)
t=Task()
t.add(tt())
t.add(gg())
t.run()#tt0#xx0#tt1#xx1#tt2#xx2#tt3#xx3
例
總結
以上是生活随笔為你收集整理的python迭代器与生成器_Python的迭代器和生成器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中cd是什么意思_pytho
- 下一篇: python with语句_python