這篇文章主要介紹了Python產生器定義與簡單用法,結合執行個體形式較為詳細的分析了Python產生器的概念、原理、使用方法及相關操作注意事項,需要的朋友可以參考下
本文執行個體講述了Python產生器定義與簡單用法。分享給大家供大家參考,具體如下:
一、什麼是產生器
在Python中,由於受到記憶體的限制,列表容量肯定是有限的。例如我們建立一個包含一億個元素的列表,Python首先會在記憶體中開闢足夠的空間來儲存這個包含一億個元素的列表,然後才允許使用者去使用這個列表,這就可能會導致以下問題:
1、記憶體中沒有足夠的記憶體空間開儲存這個列表,從而導致列表無法建立
2、即使列表成功建立,然而仍會消耗很長的時間,導致程式效率低下
3、若使用者只想訪問列表前面的幾個元素,則後面列表絕大多數元素佔用的空間就都白白浪費了
為了有效解決以上的問題,Python中引入了一種“一邊迴圈,一邊計算”的新機制,即當使用者需要使用某個對象時,Python才根據事先設計好的規則開闢記憶體空間建立這個對象供使用者使用,而不是像列表一樣事先將所有的對象都建立完畢之後再提供給使用者使用。這種機制在Python中成為產生器(generator)。
二、產生器的建立
A、產生器推到式
與列表推到式類似,只不過產生器推導式使用()而非[],並且最終返回的是產生器而非列表
g=((i+2)**2 for i in range(2,30)) #g是一個產生器print(g) #g為空白,裡麵包含任何元素
運行結果:
<generator object <genexpr> at 0x0000000002263150>
B、yield關鍵字
在一個函數定義中包含yield關鍵字,則這個函數就不再是一個普通的函數,而是一個產生器(generator)
[說明]:yield指令可以暫停一個函數並返回其中間結果,使用該指令的函數將儲存執行環境,並在必要時恢複
def fib(max): n,a,b=0,0,1 while n<max: #print(b) yield b a,b=b,a+b n+=1 return 'done'f=fib(6)print(f)
運行結果:
<generator object fib at 0x0000000002553150>
[注]:普通函數和變成產生器的函數的不同:
普通函數是順序執行的,遇到return或是最後一行函數語句就返回。而變成產生器的函數在每次調用__next__()方法時執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行
f=fib(6)print(f)print(f.__next__())print(f.__next__())print('暫停一下')print(f.__next__())print(f.__next__())
運行結果:
<generator object fib at 0x00000000025631A8>
1
1
暫停一下
2
3
三、產生器方法(參考:伯樂線上)
1.close()方法:手動關閉產生器函數,後面的調用會直接返回StopIteration異常
def func(): yield 1 yield 2 yield 3g=func()g.__next__()g.close() #手動關閉產生器g.__next__() #關閉後,yield 2和yield 3語句將不再起作用
運行結果:
Traceback (most recent call last):
File "E:\py3Demo\Hello\generatorDemo.py", line 9, in <module>
g.__next__() #關閉後,yield 2和yield 3語句將不再起作用
StopIteration
2.__next__()方法:返回產生器的下一次調用
def func(): n=1 for i in range(3): yield n n+=1c=func()a1=c.__next__()a2=c.__next__()a3=c.__next__()
[流程解釋]:
對於普通的產生器,第一個__next__()方法的調用相當於啟動產生器,此時會從產生器函數的第一行開始執行,直到第一次執行完yield語句(第四行)後,跳出產生器函數。
當調用第二個__next__()方法後,會重新進入產生器函數,並從yield語句的下一條語句(第五行)開始執行,直到重新運行到yield語句,執行後再次跳出產生器函數。
後面的__next__()方法調用以此類推
3.send()方法:接受外部傳入的一個變數,並根據變數內容計算結果返回到產生器函數中
[注]:
(1)send()方法和__next__()方法相似,區別在於send()方法可以傳遞給yield運算式值,而__next__()方法不能傳遞特定的值,只能傳遞None給yield運算式,因此可以將generator.__next__()理解為generator.send(None)
(2)第一次調用產生器函數時,必須使用__next__()語句或是send(None),不能使用send發送一個非None的值給產生器函數,否則會出錯,因為沒有yield語句來接收這個值
def gen(): value=0 while True: receive=yield value if receive=='end': break value='Got:%s' %receiveg=gen()print(g.__next__()) #或是print(g.send(None)),從而啟動產生器print(g.send('aaa'))print(g.send(3))print(g.send('end'))
運行結果:
0
Got:aaa
Got:3
Traceback (most recent call last):
File "E:\py3Demo\Hello\generatorDemo.py", line 13, in <module>
print(g.send('end'))
StopIteration
[流程解釋]:
a.通過g.send(None)或g.__next__()啟動產生器函數,並執行到第一個yield語句結束的位置並將函數掛起。此時執行完了yield語句,但是沒有給receive賦值,因此yield value會輸出value的初始值0
b.g.send('aaa')先將字串‘aaa'傳入到產生器函數中並賦值給receive,然後從yield語句的下一句重新開始執行函數(第五句),計算出value的值後返回到while頭部開始新一輪的迴圈,執行到yield value語句時停止,此時yield value會輸出‘Got:aaa',然後掛起
c.g.send(3)重複步驟b,最後輸出結果為‘Got:3'
d.g.send('end')會使程式執行break然後跳出迴圈,從而函數執行完畢,得到StopIteration異常
4.throw()方法:向產生器發送一個異常。
def gen(): while True: try: yield 'normal value' #返回中間結果,此處的yield和return的功能相似 yield 'normal value2' print('I am here') except ValueError: print('We got ValueError') except Exception: print('Other errors') breakg=gen()print(g.__next__())print(g.throw(ValueError))print(g.__next__())print(g.throw(TypeError))
運行結果:
Traceback (most recent call last):
File "E:\py3Demo\Hello\generatorDemo.py", line 17, in <module>
print(g.throw(TypeError))
StopIteration
normal value
We got ValueError
normal value
normal value2
Other errors
[解釋]:
a.print(g.__next__())會輸出normal value,並停在yield 'normal value2'之前
b.由於執行了g.throw(ValueError),所以回跳過後續的try語句,即yield ‘normal value2'不會執行,然後進入到except語句,列印出‘We got ValueError'。之後再次進入到while語句部分,消耗一個yield,輸出normal value
c.print(g.__next__())會執行yield ‘normal value2'語句,並停留在執行完該語句後的位置
d.g.throw(TypeError)會跳出try語句,因此print('I am here')不會被執行,然後列印‘Other errors',並執行break語句跳出while迴圈,然後到達程式結尾,列印StopIteration異常的資訊
四、產生器的運用
import timedef consumer(name): print('%s準備吃包子啦!' %name) while True: baozi=yield #接收send傳的值,並將值賦值給變數baozi print('包子[%s]來了,被[%s]吃了!' %(baozi,name))def producer(name): c1=consumer('A') #把函數變成一個產生器 c2=consumer('B') c1.__next__()#調用這個方法會走到yield處暫時返回 c2.__next__() print('開始準備做包子啦!') for i in range(10): time.sleep(1) print('做了一個包子,分成兩半') c1.send(i) c2.send(i)producer('Tomwenxing')
運行結果:
A準備吃包子啦!
B準備吃包子啦!
開始準備做包子啦!
做了一個包子,分成兩半
包子[0]來了,被[A]吃了!
包子[0]來了,被[B]吃了!
做了一個包子,分成兩半
包子[1]來了,被[A]吃了!
包子[1]來了,被[B]吃了!
做了一個包子,分成兩半
包子[2]來了,被[A]吃了!
包子[2]來了,被[B]吃了!
做了一個包子,分成兩半
包子[3]來了,被[A]吃了!
包子[3]來了,被[B]吃了!
做了一個包子,分成兩半
包子[4]來了,被[A]吃了!
包子[4]來了,被[B]吃了!
做了一個包子,分成兩半
包子[5]來了,被[A]吃了!
包子[5]來了,被[B]吃了!
做了一個包子,分成兩半
包子[6]來了,被[A]吃了!
包子[6]來了,被[B]吃了!
做了一個包子,分成兩半
包子[7]來了,被[A]吃了!
包子[7]來了,被[B]吃了!
做了一個包子,分成兩半
包子[8]來了,被[A]吃了!
包子[8]來了,被[B]吃了!
做了一個包子,分成兩半
包子[9]來了,被[A]吃了!
包子[9]來了,被[B]吃了!