1.什麼是生成器
以下引用廖雪峯的官方網站
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅佔用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱爲生成器:generator。
2.生成器的創建
(1)將列表生成式的【】變成()
#列表生成式,當生成時元素即打印, 會佔用內存,
In [2]: [i for i in range(1,10)]
Out[2]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
#生成器類型,並不會打印數據
In [3]: (i for i in range(1,10))
Out[3]: <generator object <genexpr> at 0x12bde10>
- 讀取生成器元素的第一個方式——–next()
#當我們把這個生成器賦給一個變量
In [4]: g = (i for i in range(1,10))
#爲該變量使用next()函數獲得generator的下一個返回值:
In [5]: g.next()
Out[5]: 1
In [6]: g.next()
Out[6]: 2
generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。
- 讀取生成器元素的第二個方式——for循環
#生成器也是可迭代對象,可以使用for循環
In [8]: g = (i for i in range(1,10))
In [9]: for i in g:
print i
...:
1
2
3
4
5
6
7
8
9
(2)定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那麼這個函數就不再是一個普通函數,而是一個generator:
比如,著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數都可由前兩個數相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, …
def fib(max):
n,a,b = 0,0,1
while n < max:
yield b
a,b = b,a+b
n +=1
g = fib(6)
for i in g:
print i
Tips—–python中的兩值交換
x=3、y=4
執行:
x, y = y, x
實質上是 先構造右邊的元組(y,x),即(4,3),然後將元組的值依次賦給x,y;
回到Fibonacci的例子上,把函數改成generator後,我們基本上從來不會用next()來獲取下一個返回值,而是直接使用for循環來迭代。
那麼該generator的函數的執行流程是:
每次在遇到for循環(可以理解爲每次讀取生成器元素的時候)的時候執行函數(也就是fib(max)函數),遇到yield語句返回,再次執行時從上次返回的的yield語句處執行,讀者可以將上面的程序單步調試,就能更深刻理解。
3.生成器的應用
(1)實現生產者消費者模型(有無緩衝區)
???什麼時生產者消費者模型
生產者和消費者之間用中間類似一個隊列一樣的東西串起來。這個隊列可以想像成一個存放產品的“倉庫”,生產者只需要關心這個“倉庫”,並不需要關心具體的消費者,對於生產者而言甚至都不知道有這些消費者存在。對於消費者而言他也不需要關心具體的生產者,到底有多少生產者也不是他關心的事情,他只要關心這個“倉庫”中還有沒有東西。
- 無緩衝區(沒有中間商)
#生成器
def consumer(name):
print "[%s]準備買sugar" %(name)
while True:
size = yield
print "客戶[%s]買[%s]包裝的sugar" %(name,size)
#引用time模塊,纔可以使用下面的sleep方法
import time
def producer(name,*size):
#調用生成器賦給變量c1
c1 = consumer("user1")
c2 = consumer("user2")
#c1使用next()方法後纔可以執行consumer(),遇到yield停止
c1.next()
c2.next()
print "準備製作sugar....."
for i in size:
time.sleep(1) #等待一秒
print "[%s]製作了[%s]包裝的sugar提供給消費者" %(name,i)
#從上次停止的yield處繼續執行
c1.send(i) #send 方法將值傳給 yield 所在位置的變量
c2.send(i)
producer("producer1","大","中","小")
運行效果:
[user1]準備買sugar
[user2]準備買sugar
準備製作sugar.....
[producer1]製作了[大]包裝的sugar提供給消費者
客戶[user1]買[大]包裝的sugar
客戶[user2]買[大]包裝的sugar
[producer1]製作了[中]包裝的sugar提供給消費者
客戶[user1]買[中]包裝的sugar
客戶[user2]買[中]包裝的sugar
[producer1]製作了[小]包裝的sugar提供給消費者
客戶[user1]買[小]包裝的sugar
客戶[user2]買[小]包裝的sugar
- 有緩衝區(有中間商)
#定義一箇中間商隊列
sugar_agency = []
def consumer(name):
print "[%s]準備買sugar" %(name)
while True:
size = yield
#將user已經買過的size從sugar_agency隊列中移除
sugar_agency.remove(size)
print "客戶[%s]買[%s]包裝的sugar" %(name,size)
def producer(name,*size):
print "準備製作sugar....."
for i in size:
time.sleep(1)
print "[%s]製作了[%s]包裝的sugar提供給消費者" %(name,i)
sugar_agency.append(i)
producer("producer1","大","中","小") #順序執行完producer()
c1 = consumer("user1") #調用生成器賦給變量c1
c1.next() #從上次停止的yield處繼續執行
#send 方法將值傳給 yield 所在位置的變量
c1.send("大")
c2 = consumer("user2")
c2.next()
c2.send("中")
print "還剩包裝:" #顯示user購買完後,還剩的size
for i in sugar_agency:
print i
運行效果:
準備製作sugar.....
[producer1]製作了[大]包裝的sugar提供給消費者
[producer1]製作了[中]包裝的sugar提供給消費者
[producer1]製作了[小]包裝的sugar提供給消費者
[user1]準備買sugar
客戶[user1]買[大]包裝的sugar
[user2]準備買sugar
客戶[user2]買[中]包裝的sugar
還剩包裝:
小
(2)聊天機器人
def chat_robot():
res = ""
while True:
received = yield res
if "你好" in received or "hi" in received:
print "你好呀!!!歡迎來到yueer的世界"
elif "笨" in received or "傻" in received:
print "請文明用語哦!!!我會生氣的,哼~~~"
elif "性別" in received or "女" in received:
print "本姑娘還小,沒有談過戀愛!!!傲嬌臉"
elif "我的媽呀" in received or "天" in received:
print "不要吃驚,我就是如此聰慧!!!嘟嘟嘟"
else:
res = "寶寶不明白你在說什麼???"
chat = chat_robot()
next(chat) #開始執行
while True:
c = raw_input(">>:")
if not c:
continue
elif c.lower() == "q":
print "byebye ,see you next time!!!lalala"
break
else:
#send方法將用戶輸入傳給yield,根據用戶輸入作出判斷,將返回值賦給response
response = chat.send(c)
#打印response
print response
運行效果:
>>:你好呀,小機器人
你好呀!!!歡迎來到yueer的世界
>>:你是男孩還是女孩呀
本姑娘還小,沒有談過戀愛!!!傲嬌臉
>>:我的媽呀,自戀
不要吃驚,我就是如此聰慧!!!嘟嘟嘟
>>:聰慧嗎,笨豬豬
請文明用語哦!!!我會生氣的,哼~~~
>>:好啊,你比較聰明~
寶寶不明白你在說什麼???
>>:q
byebye ,see you next time!!!lalala