南京網(wǎng)站搭建公司網(wǎng)絡(luò)推廣公司專業(yè)網(wǎng)絡(luò)
大家好,小編來為大家解答以下問題,python生成器有幾種寫法,python生成器函數(shù)例子,今天讓我們一起來看看吧!
本文部分參考:Python迭代器,生成器–精華中的精華 https://www.cnblogs.com/deeper/p/7565571.html
一 迭代器和可迭代對象
迭代器是訪問集合元素的一種方式?;疖囶^采集器AI偽原創(chuàng)。迭代器只能往前不會(huì)后退。迭代器的一大優(yōu)點(diǎn)是不要求事先準(zhǔn)備好整個(gè)迭代過程中所有的元素,僅僅在迭代到某個(gè)元素時(shí)才計(jì)算該元素,而在這之前或之后,元素可以不存在或者被銷毀。這個(gè)特點(diǎn)使得它特別適合用于遍歷一些巨大的或是無限的集合,比如幾個(gè)G的文件。
特點(diǎn):a)訪問者不需要關(guān)心迭代器內(nèi)部的結(jié)構(gòu),僅需通過next()方法或不斷去取下一個(gè)內(nèi)容
b)不能隨機(jī)訪問集合中的某個(gè)值 ,只能從頭到尾依次訪問
c)訪問到一半時(shí)不能往回退
d)便于循環(huán)比較大的數(shù)據(jù)集合,節(jié)省內(nèi)存
e)也不能復(fù)制一個(gè)迭代器。如果要再次(或者同時(shí))迭代同一個(gè)對象,只能去創(chuàng)建另一個(gè)迭代器對象。
enumerate()的返回值就是一個(gè)迭代器,我們以enumerate為例:
a = enumerate(['a','b'])for i in range(2): #迭代兩次enumerate對象for x, y in a:print(x,y)print(''.center(50,'-'))
結(jié)果:
0 a
1 b
-----------------------分割線------------------------
-----------------------分割線------------------------
可以看到再次迭代enumerate對象時(shí),沒有返回值。
我們可以用linux的文件處理命令vim和cat來理解一下:
a) 讀取很大的文件時(shí),vim需要很久,cat是毫秒級;因?yàn)関im是一次性把文件全部加載到內(nèi)存中讀取;而cat是加載一行顯示一行
b) vim讀寫文件時(shí)可以前進(jìn),后退,可以跳轉(zhuǎn)到任意一行;而cat只能向下翻頁,不能倒退,不能直接跳轉(zhuǎn)到文件的某一頁(因?yàn)樽x取的時(shí)候這個(gè)“某一頁“可能還沒有加載到內(nèi)存中)。
正式進(jìn)入python迭代器之前,我們先要區(qū)分兩個(gè)容易混淆的概念:可迭代對象(Iterable)和迭代器(Iterator)。
1.1可迭代對象
定義:迭代器是一個(gè)對象,不是一個(gè)函數(shù)。只要它定義了可以返回一個(gè)迭代器的__iter__方法,或者定義了可以支持下標(biāo)索引的__getitem__方法,那么它就是一個(gè)可迭代對象。
注意: 集合數(shù)據(jù)類型,如list、tuple、dict、set、str都是可迭代對象Iterable,卻不是迭代器Iterator。
如何判斷一個(gè)對象是可迭代對象呢?可以通過collections模塊的Iterable類型判斷:
from collections import Iterable
print(isinstance([], Iterable))
print(isinstance({}, Iterable))
print(isinstance('abc', Iterable))
print(isinstance({'name':'join','age':23},Iterable))
print(isinstance(set([2,3]),Iterable))
結(jié)果為:
True
True
True
True
True
再看:
from collections import Iterator
print(isinstance([], Iterator))
print(isinstance({}, Iterator))
print(isinstance('abc', Iterator))
print(isinstance({'name':'join','age':23},Iterator))
print(isinstance(set([2,3]),Iterator))
結(jié)果為:
False
False
False
False
False
1.2迭代器
定義:任何實(shí)現(xiàn)了__iter__()和__next__()(python2中實(shí)現(xiàn)next())方法的對象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一個(gè)值。
這里我們來看一下迭代器和可迭代對象的使用方法。
問題:不適用for,while以及下表索引,怎么遍歷一個(gè)list?
l1=[2,3,5]
iter_l1 = l1.__iter__()
# 或者iter(iter_l1)
# iter(iter_l1)是python的內(nèi)置函數(shù),
# l1.__iter__()調(diào)用的是l1對象的__iter__()方法。
# 下面的next()函數(shù)和__iter__()函數(shù)類似。
print(next(iter_l1)) # 或print(iter_l1.__next__()
print(next(iter_l1))
print(next(iter_l1))
print(next(iter_l1))
結(jié)果為:
2
3
5
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-126-eb58865cb644> in <module>4 print(next(iter_l1))5 print(next(iter_l1))
----> 6 print(next(iter_l1))
結(jié)論:思路就是,先得到當(dāng)前對象(當(dāng)前對象是可迭代對象)的迭代器,然后每次執(zhí)行next(iter_l1)就可以得到當(dāng)前對象的一個(gè)值。
修改一下:
l1=[2,3,5]
iter_l1 = l1.__iter__()
for i in iter_l1:print(i,end=',')
結(jié)果為:
2,3,5,
再次執(zhí)行for語句如下:
for i in iter_l1:print(i,end=',')
此次返回結(jié)果為空,可以看到迭代器只能遍歷一次對象,不能重復(fù)遍歷。由于for語句可以自動(dòng)處理StopIteration異常,所以這里沒有報(bào)出StopIteration,而是沒有任何結(jié)果。
看一個(gè)例子。我們想得到Fibonacci數(shù)列,思路是定義一個(gè)可迭代對象類Fib;然后在定義一個(gè)迭代器類FibIterator,使用方式是:
① 實(shí)例化一個(gè)可迭代對象: fib = Fib()
② 得到fib的一個(gè)迭代器: fib_iter = iter(fib)
③ 然后每次調(diào)用next(fib_iter)可得到fibonacci數(shù)列中的一個(gè)數(shù)。
class FibIterator():'''定義迭代器類'''def __init__(self,num,a,b,current):self.num = numself.a = aself.b = bself.current = currentdef __iter__(self):return selfdef __next__(self):if(self.num-1>=0):self.num = self.num-1self.current = self.aself.a = self.bself.b = self.b+self.current #以上兩步賦值操作可省略中間變量直接寫為self.a,self.b = self.b,self.a+self,b return self.currentelse: raise StopIterationclass Fib:'''定義可迭代對象所屬類'''def __init__(self,num): #num表示該數(shù)列的長度self.a = 1self.b = 2self.current=self.aself.num = numdef __iter__(self):return FibIterator(self.num,self.a,self.b,self.current)fib = Fib(20)
fib_iter = iter(fib)
for i in range(20):print(next(fib_iter),end=',')
結(jié)果為:
1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,
這里定義的Fibonacci類的__init__函數(shù)有一個(gè)參數(shù)num,表示一共得到幾個(gè)fibonacci數(shù)。
由于可迭代對象是實(shí)現(xiàn)了__iter__()方法的,迭代器對象是實(shí)現(xiàn)了__iter__()和__next__()方法的,能否只定義一個(gè)迭代器類呢?
試一下。
class Fib:def __init__(self,num):self.num = numself.a = 1self.b = 2self.current = self.adef __iter__(self):return selfdef __next__(self):if(self.num-1>=0):self.num = self.num-1self.current = self.aself.a = self.bself.b = self.b+self.current #以上兩步賦值操作可省略中間變量直接寫為self.a,self.b = self.b,self.a+self,b return self.currentelse: raise StopIterationfib = Fib(10)
for i in fib:print(i,end=',')
結(jié)果為:
1,2,3,5,8,13,21,34,55,89,
當(dāng)我們再次執(zhí)行for語句時(shí),
for i in fib:print(i,end=',')
同上面的分析一樣,結(jié)果為空。如果想再次得到fibonacci數(shù)列的前10個(gè)數(shù),就必須重新實(shí)例化Fib對象。
結(jié)論:為什么不只保留Iterator的接口而還需要設(shè)計(jì)Iterable呢?
因?yàn)榈鞯淮我院缶涂樟?#xff0c;那么如果list,dict也是一個(gè)迭代器,迭代一次就不能再繼續(xù)被迭代了,這顯然是反人類的;所以通過__iter__每次返回一個(gè)獨(dú)立的迭代器,就可以保證不同的迭代過程不會(huì)互相影響。
另外,迭代器是惰性的,只有在需要返回下一個(gè)數(shù)據(jù)時(shí)它才會(huì)計(jì)算。所以,Iterator甚至可以表示一個(gè)無限大的數(shù)據(jù)流,例如全體自然數(shù)。而使用list是永遠(yuǎn)不可能存儲全體自然數(shù)的。
下面的例子得到全體自然數(shù)(下面我們用生成器可以得到更簡單的寫法)。
class Natural:def __init__(self):passdef __iter__(self):return NaturalIterator()
class NaturalIterator:def __init__(self):self.beg=0self.current=self.begdef __iter__(self):return selfdef __next__(self):self.current += 1return self.current # 顯示前20個(gè)自然數(shù)
n1=Natural()
n1_iter = iter(n1)
for i in range(20):print(next(n1_iter),end=',')
結(jié)果為:
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
作業(yè):使用迭代器和可迭代對象實(shí)現(xiàn)得到全體fibonacci數(shù)。
二 生成器
2.1 生成器的定義和使用
定義:生成器其實(shí)是一種特殊的迭代器,它不需要再像上面的類一樣寫__iter__()和__next__()方法了,只需要一個(gè)yiled關(guān)鍵字。
生成器一定是迭代器(反之不成立)。
Python有兩種不同的方式提供生成器:
- 生成器函數(shù):常規(guī)函數(shù)定義,但是,使用yield語句而不是return語句返回結(jié)果。 yield語句一次返回一個(gè)結(jié)果,在每個(gè)結(jié)果中間,掛起函數(shù)的狀態(tài),以便下次重它離開的地方繼續(xù)執(zhí)行
- 生成器表達(dá)式:類似于列表推導(dǎo),但是,生成器返回按需產(chǎn)生結(jié)果的一個(gè)對象, 而不是一次構(gòu)建一個(gè)結(jié)果列表
我們先用一個(gè)例子說明一下:
def generate_even():i=0while True:if i%2==0:yield ii += 1g=generate_even()
dir(g) # 可以看到里面有__iter__()方法和__next__()方法,所以生成器也是迭代器。for i in range(10):print(g.__next__(),end=',')
結(jié)果為:
0,2,4,6,8,10,12,14,16,18,
現(xiàn)在解釋一下上面的代碼:
我們知道,一個(gè)函數(shù)只能返回一次,即return以后,這次函數(shù)調(diào)用就結(jié)束了;
但是生成器函數(shù)可以暫停執(zhí)行,并且通過yield返回一個(gè)中間值,當(dāng)生成器對象的__next__()方法再次被調(diào)用的時(shí)候,生成器函數(shù)可以從上一次暫停的地方繼續(xù)執(zhí)行,直到下一次遇到y(tǒng)ield語句(此時(shí)會(huì)返回yield后面的值,如果有的話)或者觸發(fā)一個(gè)StopIteration。
了解協(xié)同程序:
a) 生成器的另外一個(gè)方面是協(xié)同程序的概念。協(xié)同程序是可以運(yùn)行的獨(dú)立函數(shù)調(diào)用,可以暫停或者掛起,并從程序離開的地方繼續(xù)或者重新開始。
b) 可以在調(diào)用者和被調(diào)用的之間協(xié)同程序通信。
c) 在程序暫停時(shí)可以傳參:舉例來說,當(dāng)協(xié)同程序暫停時(shí),我們?nèi)钥梢詮钠渲蝎@得一個(gè)中間的返回值,當(dāng)調(diào)用回到程序中時(shí),能夠傳入額外或者改變了的參數(shù),但是仍然能夠從我們上次離開的地方繼續(xù),并且所有狀態(tài)完整。
生成器表達(dá)式類似于列表推導(dǎo)式,只是把[]換成(),這樣就創(chuàng)建了一個(gè)生成器。
gen = (x for x in range(10))
下面我們用生成器來實(shí)現(xiàn)前面的fibonacci數(shù)列和全體自然數(shù)。
# 生成前n個(gè)fibonacci數(shù)
def fib(n):a, b = 0, 1count=0while True:if(count>n):breakcount += 1yield ba, b = b, a+bf = fib(20)
for item in f:print(item,end=',')
結(jié)果為:
1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,
def Natural():n=0while True:yield nn += 1
g_n1=Natural()
for i in range(20):print(next(g_n1),end=',')
結(jié)果為:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
2.2 send方法
生成器函數(shù)最大的特點(diǎn)是可以接受外部傳入的一個(gè)變量,并根據(jù)變量內(nèi)容計(jì)算結(jié)果后返回。這是生成器函數(shù)最難理解的地方,也是最重要的地方,協(xié)程的實(shí)現(xiàn)就全靠它了。
看一個(gè)小貓吃魚的例子:
def cat():print('我是一只hello kitty')while True:food = yieldif food == '魚肉':yield '好開心'else:yield '不開心,人家要吃魚肉啦'
中間有個(gè)賦值語句food = yield,可以通過send方法來傳參數(shù)給food,試一下:
情況1)
miao = cat() #只是用于返回一個(gè)生成器對象,cat函數(shù)不會(huì)執(zhí)行
print(''.center(50,'-'))
print(miao.send('魚肉'))
結(jié)果:
Traceback (most recent call last):
--------------------------------------------------File "C:/Users//Desktop/Python/cnblogs/subModule.py", line 67, in <module>print(miao.send('魚肉'))
TypeError: can't send non-None value to a just-started generator
看到了兩個(gè)信息:
a)miao = cat() ,只是用于返回一個(gè)生成器對象,cat函數(shù)不會(huì)執(zhí)行
b)can’t send non-None value to a just-started generator;不能給一個(gè)剛創(chuàng)建的生成器對象直接send值
改一下,情況2)
miao = cat()
miao.__next__()
print(miao.send('魚肉'))
那么到底send()做了什么呢?send()的幫助文檔寫的很清楚,’’‘Resumes the generator and “sends” a value that becomes the result of the current yield-expression.’’’;可以看到send依次做了兩件事:
a)回到生成器掛起的位置,繼續(xù)執(zhí)行b)并將send(arg)中的參數(shù)賦值給對應(yīng)的變量,如果沒有變量接收值,那么就只是回到生成器掛起的位置
但是,我認(rèn)為send還做了第三件事:
c)兼顧__next__()作用,掛起程序并返回值,所以我們在print(miao.send(‘魚肉’))時(shí),才會(huì)看到’好開心’;其實(shí)__next__()等價(jià)于send(None)
所以當(dāng)我們嘗試這樣做的時(shí)候:
def cat():print('我是一只hello kitty')while True:food = yieldif food == '魚肉':yield '好開心'else:yield '不開心,人家要吃魚肉啦'miao = cat()
print(miao.__next__())
print(miao.send('魚肉'))
print(miao.send('骨頭'))
print(miao.send('雞肉'))
就會(huì)得到這個(gè)結(jié)果:
我是一只hello kitty
None
好開心
None
不開心,人家要吃魚肉啦
我們按步驟分析一下:
a)執(zhí)行到print(miao.next()),執(zhí)行cat()函數(shù),print了”我是一只hello kitty”,然后在food = yield掛起,并返回了None,打印None
b)接著執(zhí)行print(miao.send(‘魚肉’)),回到food = yield,并將’魚肉’賦值給food,生成器函數(shù)恢復(fù)執(zhí)行;直到運(yùn)行到y(tǒng)ield ‘好開心’,程序掛起,返回’好開心’,并print’好開心’
c)接著執(zhí)行print(miao.send(‘骨頭’)),回到y(tǒng)ield ‘好開心’,這時(shí)沒有變量接收參數(shù)’骨頭’,生成器函數(shù)恢復(fù)執(zhí)行;直到food = yield,程序掛起,返回None,并print None
d)接著執(zhí)行print(miao.send(‘雞肉’)),回到food = yield,并將’雞肉’賦值給food,生成器函數(shù)恢復(fù)執(zhí)行;直到運(yùn)行到y(tǒng)ield’不開心,人家要吃魚肉啦’,程序掛起,返回’不開心,人家要吃魚肉啦’,并print ‘不開心,人家要吃魚肉啦’
大功告成,那我們優(yōu)化一下代碼:
def cat():msg = '我是一只hello kitty'while True:food = yield msgif food == '魚肉':msg = '好開心'else:msg = '不開心,人家要吃魚啦'miao = cat()
print(miao.__next__())
print(miao.send('魚肉'))
print(miao.send('雞肉'))
我們再看一個(gè)更實(shí)用的例子,一個(gè)計(jì)數(shù)器。
def counter(start_at = 0):count = start_atwhile True:val = yield countif val is not None:count = valelse:count += 1count = counter(5)
print(count.__next__())
print(count.send(0))
結(jié)果為5,0而不是5,6的原因:
①執(zhí)行print(count.next()),程序運(yùn)行到val = yield count(第一個(gè)yield語句)后掛起,然后返回yield后面的值,所以結(jié)果為5。
②執(zhí)行print(count.send(0)),程序恢復(fù)到掛起點(diǎn)val = yield count,將send的參數(shù)0賦值給接受變量val,然后繼續(xù)執(zhí)行下面的語句,由于val=0,所以if val is not None條件為真,count = val,接著又來到y(tǒng)ield語句(val = yield count),此時(shí)程序掛起,返回yield后面的值count=0。
綜上:當(dāng)執(zhí)行next()方法時(shí),程序會(huì)恢復(fù)到掛起點(diǎn),依次執(zhí)行yield語句下面的語句,最后再返回yield后面的值;而不是先返回yield后面的值,再執(zhí)行yield后面的語句。
最后給出一張圖說明Iterable,Iterator,Generator的關(guān)系:
補(bǔ)充幾個(gè)小例子:
a)使用生成器創(chuàng)建一個(gè)range
def range(n):count = 0while count < n:yield countcount += 1