1. 程式人生 > >Python yield使用詳解(一)

Python yield使用詳解(一)

生成器

yield語句可以作為生成器

def countdown(n):
    while n > 0:
        yield n
        n -= 1
        
# 可以當迭代器來使用它
for x in countdown(10):
    print('T-minus', x)
    
# 可以使用next()來產出值,當生成器函式return(結束)時,報錯。
>>> 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作為協程的使用方法,作為生成器的話我一筆帶過,想要仔細瞭解迭代器生成器使用,我這裡推薦個教程。完全理解Python迭代物件、迭代器、生成器 ,很棒,還有的話就是與生成器密切相關的itertools模組,可以瞭解下。但是我在講yield協程之前我再給出一張圖來說yield一個有趣的用法。

生成器類似於UNIX管道的作用

 

這個process會有難以置信的作用,比如實現UNIX中grep的作用。不展開,以後肯定會用到它。

生成器進化為協程

一個協程例子

重頭戲來了。
如果你想更多的使用yield,那麼就是協程了。協程就不僅僅是產出值了,而是能消費傳送給它的值。


那麼這裡的例子就用協程實現上面的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>  
# 啟用協程!只能用一次,也可以用g.send(None)來代替next(g)
>>> next(g)
Looking for python
# 使用.send(...)傳送資料,傳送的資料會成為生成器函式中yield表示式值,即變數line的值
>>> g.send("Yeah, but no, but yeah, but no")

>>> g.send("A series of tubes")
# 協程,協程,就是互相協作的程式,我發資料過去然後你協助我一下看看grep成功沒
>>> g.send("python generators rock!")
python generators rock! : grep success 
# 關閉
>>> g.close()

例子講完了。有幾個注意點:

  • 生成器用於生成供迭代的資料
  • 協程是資料的消費者
  • 為了避免腦袋炸裂,不能把兩個概念混為一談
  • 協程與迭代無關
  • 注意,雖然在協程值會使用yield產出值,但這與迭代無關

傳送資料給協程

預啟用,到yield處暫停。然後傳送item值,協程繼續了,協程中item接收到傳送的那個值,然後到下一個yield再暫停。

使用一個裝飾器

如果不預激(primer),那麼協程沒什麼用,呼叫g.send(x)之前。記住一定要呼叫next(g)。為了簡化協程用法,有時會使用一個預激裝飾器,如下。

def coroutine(func):
    def primer(*args,**kwargs):
        cr = func(*args,**kwargs)
        next(cr)
        return cr
    return primer
    
@coroutine
def grep(pattern):
 ...  

關閉一個協程

  • 一個協程有可能永遠執行下去
  • 可以 .close()讓它停下來
    例子中已經體現,不展開。

捕捉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
>>>

說明:

  • 在協程內部能丟擲一個異常
  • 異常發生於yield表示式
  • 不慌,我們可以平常的方法處理它

生成器返回數值

鑑於上面的例子是一直run下去的,所以稍加修改:

def grep(pattern):
    print("Looking for {}".format(pattern))
    while True:
        line = yield
        # 當傳送的資料為None時,跳出while迴圈
        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

總結

  1. yield的基本用法已經差不多了,有兩個方面:生成器與協程(理解協程的關鍵在於明白它在何處暫停傳送出的資料傳到了哪個變數)
  2. yield的另一方面的應用是上下文管理器下一節講
  3. yield from我這裡暫時不講,留到後面。yield from會在內部自動捕獲StopIteration異常等

參考資料

David beazley協程
Fluent Python



作者:尋找無雙丶
連結:https://www.jianshu.com/p/2ed9a88f5769
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。