生成器
yield語句可以作為生成器
def countdown(n):
while n > 0:
yield n
n -= 1
# 可以當?shù)鱽硎褂盟?for x in countdown(10):
print('T-minus', x)
# 可以使用next()來產(chǎn)出值椿息,當生成器函數(shù)return(結(jié)束)時汛聚,報錯赐稽。
>>> c = countdown(3)
>>> c
<generator object countdown at 0x10064f900>
>>> next(c)
3
>>> next(c)
2
>>> next(c)
1
>>> next(c)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration
>>>
這篇文章我著重講yield作為協(xié)程
的使用方法塑荒,作為生成器的話我一筆帶過忽妒,想要仔細了解迭代器
與生成器
使用玩裙,我這里推薦個教程。完全理解Python迭代對象段直、迭代器吃溅、生成器 ,很棒鸯檬,還有的話就是與生成器密切相關的itertools模塊决侈,可以了解下。但是我在講yield協(xié)程
之前我再給出一張圖來說yield一個有趣的用法喧务。
生成器類似于UNIX管道的作用
這個process會有難以置信的作用赖歌,比如實現(xiàn)UNIX中grep的作用枉圃。不展開,以后肯定會用到它庐冯。
生成器進化為協(xié)程
一個協(xié)程例子
重頭戲來了孽亲。
如果你想更多的使用yield,那么就是協(xié)程了展父。協(xié)程就不僅僅是產(chǎn)出值了墨林,而是能消費發(fā)送給它的值。
那么這里的例子就用協(xié)程實現(xiàn)上面的UNIX的grep作用
def grep(pattern):
print("Looking for {}".format(pattern))
while True:
line = yield
if pattern in line:
print('{} : grep success '.format(line))
>>> g=grep('python')
# 還是個生成器
>>> g
<generator object grep at 0x7f17e86f3780>
# 激活協(xié)程犯祠!只能用一次,也可以用g.send(None)來代替next(g)
>>> next(g)
Looking for python
# 使用.send(...)發(fā)送數(shù)據(jù)酌呆,發(fā)送的數(shù)據(jù)會成為生成器函數(shù)中yield表達式值衡载,即變量line的值
>>> g.send("Yeah, but no, but yeah, but no")
>>> g.send("A series of tubes")
# 協(xié)程,協(xié)程隙袁,就是互相協(xié)作的程序痰娱,我發(fā)數(shù)據(jù)過去然后你協(xié)助我一下看看grep成功沒
>>> g.send("python generators rock!")
python generators rock! : grep success
# 關閉
>>> g.close()
例子講完了。有幾個注意點:
- 生成器用于生成供迭代的數(shù)據(jù)
- 協(xié)程是數(shù)據(jù)的消費者
- 為了避免腦袋炸裂菩收,不能把兩個概念混為一談
- 協(xié)程與迭代無關
- 注意梨睁,雖然在協(xié)程值會使用yield產(chǎn)出值,但這與迭代無關
發(fā)送數(shù)據(jù)給協(xié)程
預激活娜饵,到y(tǒng)ield處暫停坡贺。然后發(fā)送item值,協(xié)程繼續(xù)了箱舞,協(xié)程中item接收到發(fā)送的那個值遍坟,然后到下一個yield再暫停。
使用一個裝飾器
如果不預激(primer
),那么協(xié)程沒什么用晴股,調(diào)用g.send(x)之前愿伴。記住一定要調(diào)用next(g)。為了簡化協(xié)程用法电湘,有時會使用一個預激裝飾器隔节,如下。
def coroutine(func):
def primer(*args,**kwargs):
cr = func(*args,**kwargs)
next(cr)
return cr
return primer
@coroutine
def grep(pattern):
...
關閉一個協(xié)程
- 一個協(xié)程有可能永遠運行下去
- 可以 .close()讓它停下來
例子中已經(jīng)體現(xiàn)寂呛,不展開怎诫。
捕捉close()
def grep(pattern):
print("Looking for {}".format(pattern))
try:
while True:
line = yield
if pattern in line:
print(line)
except GeneratorExit:
print("Going away. Goodbye")
捕捉到.close()方法,然后會打印"Going away. Goodbye"
昧谊。
拋出異常
>>> g = grep("python")
>>> next(g) # Prime it
Looking for python
>>> g.send("python generators rock!")
python generators rock! : grep success
>>> g.throw(RuntimeError,"You're hosed")
Traceback (most recent call last):
.....
.....
RuntimeError: You're hosed
>>>
說明:
- 在協(xié)程內(nèi)部能拋出一個異常
- 異常發(fā)生于yield表達式
- 不慌刽虹,我們可以平常的方法處理它
生成器返回數(shù)值
鑒于上面的例子是一直run下去的,所以稍加修改:
def grep(pattern):
print("Looking for {}".format(pattern))
while True:
line = yield
# 當發(fā)送的數(shù)據(jù)為None時呢诬,跳出while循環(huán)
if line is None:
break
else:
if pattern in line:
print('{} : grep success '.format(line))
return 'End'
>>> ..... 省略
>>> g.send(None)
Traceback (most recent call last):
...
...
StopIteration: End
# 這里可以用try捕捉異常涌哲,異常對象的value屬性保存著返回的值
try:
g.send(None)
except StopIteration as exc:
result = exc.value
>>> result
End
圖解如下
說明:
- 通過捕捉異常獲取返回值
- 只支持python3
總結(jié)
-
yield
的基本用法已經(jīng)差不多了胖缤,有兩個方面:生成器與協(xié)程(理解協(xié)程的關鍵在于明白它在何處暫停發(fā)送出的數(shù)據(jù)傳到了哪個變量) -
yield
的另一方面的應用是上下文管理器下一節(jié)講 -
yield from
我這里暫時不講,留到后面阀圾。yield from
會在內(nèi)部自動捕獲StopIteration
異常等
參考資料
David beazley協(xié)程
Fluent Python