Python中的產生器和yield詳細介紹

來源:互聯網
上載者:User
列表推導與產生器運算式

當我們建立了一個列表的時候,就建立了一個可以迭代的對象:

代碼如下:


>>> squares=[n*n for n in range(3)]
>>> for i in squares:
print i

0
1
4


這種建立列表的操作很常見,稱為列表推導。但是像列表這樣的迭代器,比如str、file等,雖然用起來很方便,但有一點,它們是儲存在記憶體中的,如果值很大,會很麻煩。

而產生器運算式不同,它執行的計算與列表包含相同,但會迭代的產生結果。它的文法與列表推導一樣,只是要用小括弧來代替中括弧:

代碼如下:


>>> squares=(n*n for n in range(3))
>>> for i in squares:
print i

0
1
4


產生器運算式不會建立序列形式的對象,不會把所有的值都讀取到記憶體中,而是會建立一個通過迭代並按照需求產生值的產生器對象(Generator)。

那麼,還有沒有其它方法來產生產生器呢?

例子:斐波那契數列

例如有個需求,要產生斐波那契數列的前10位,我們可以這樣寫:

代碼如下:


def fib(n):
result=[]
a=1
b=1
result.append(a)
for i in range(n-1):
a,b=b,a+b
result.append(a)
return result
if __name__=='__main__':
print fib(10)


數字很少時,函數運行良好,但數字很多時,問題就來了,顯然產生一個幾千幾萬長度的列表並不是一個很好的主意。

這樣,需求就變成了:寫一個可以產生可迭代對象的函數,或者說,不要讓函數一次返回全部的值,而是一次返回一個值。

這好像與我們的常識相違背,當我們調用一個普通的Python函數時,一般是從函數的第一行代碼開始執行,結束於return語句、異常或者函數結束(可以看作隱式的返回None):

代碼如下:


def fib(n):
a=1
b=1
for i in range(n-1):
a,b=b,a+b
return a
if __name__=='__main__':
print fib(10)
>>>
1 #返回第一個值時就卡住了


函數一旦將控制權交還給調用者,就意味著全部結束。函數中做的所有工作以及儲存在局部變數中的資料都將丟失。再次調用這個函數時,一切都將從頭建立。函數只有一次返回結果的機會,因而必須一次返回所有的結果。通常我們都這麼認為的。但是,如果它們並非如此呢?請看神奇的yield:

代碼如下:


def fib(n):
a=1
yield a
b=1
for i in range(n-1):
a,b=b,a+b
yield a
if __name__=='__main__':
for i in fib(10):
print i
>>>
1
1
2
3
5
8
13
21
34

產生器Generator

python中產生器的定義很簡單,使用了yield關鍵字的函數就可以稱之為產生器,它產生一個值的序列:

代碼如下:


def countdown(n):
while n>0:
yield n
n-=1
if __name__=='__main__':
for i in countdown(10):
print i


產生器函數返回產生器。要注意的是產生器就是一類特殊的迭代器。作為一個迭代器,產生器必須要定義一些方法,其中一個就是__next__()。如同迭代器一樣,我們可以使用next()函數(Python3是__next__() )來擷取下一個值:

代碼如下:


>>> c=countdown(10)
>>> c.next()
10
>>> c.next()
9


每當產生器被調用的時候,它會返回一個值給調用者。在產生器內部使用yield來完成這個動作。為了記住yield到底幹了什麼,最簡單的方法是把它當作專門給產生器函數用的特殊的return。調用next()時,產生器函數不斷的執行語句,直至遇到yield為止,此時產生器函數的”狀態”會被凍結,所有的變數的值會被保留下來,下一行要執行的代碼的位置也會被記錄,直到再次調用next()繼續執行yield之後的語句。

next()不能無限執行,當迭代結束時,會拋出StopIteration異常。迭代未結束時,如果你想結束產生器,可以使用close()方法。

代碼如下:


>>> c.next()
1
>>> c.next()
StopIteration
>>> c=countdown(10)
>>> c.next()
10
>>> c.close()
>>> c.next()
StopIteration


協程與yield運算式

yield語句還有更給力的功能,作為一個語句出現在賦值運算子的右邊,接受一個值,或同時產生一個值並接受一個值。

代碼如下:


def recv():
print 'Ready'
while True:
n=yield
print 'Go %s'%n
>>> c=recv()
>>> c.next()
Ready
>>> c.send(1)
Go 1
>>> c.send(2)
Go 2


以這種方式使用yield語句的函數稱為協程。在這個例子中,對於next()的初始調用是必不可少的,這樣協程才能執行可通向第一個yield運算式的語句。在這裡協程會掛起,等待相關產生器對象send()方法給它發送一個值。傳遞給send()的值由協程中的yield運算式返回。

協程的運行一般是無限期的,使用方法close()可以顯式的關閉它。

如果yield運算式中提供了值,協程可以使用yield語句同時接收和發出傳回值。

代碼如下:


def split_line():
print 'ready to split'
result=None
while True:
line=yield result
result=line.split()
>>> s=split_line()
>>> s.next()
ready to split
>>> s.send('1 2 3')
['1', '2', '3']
>>> s.send('a b c')
['a', 'b', 'c']


注意:理解這個例子中的先後順序非常重要。首個next()方法讓協程執行到yield result,這將返回result的值None。在接下來的send()調用中,接收到的值被放到line中並拆分到result中。send()方法的傳回值就是下一條yield語句的值。也就是說,send()方法可以將一個值傳遞給yield運算式,但是其傳回值來自下一個yield運算式,而不是接收send()傳遞的值的yield運算式。

如果你想用send()方法來開啟協程的執行,必須先send一個None值,因為這時候是沒有yield語句來接受值的,否則就會拋出異常。

代碼如下:


>>> s=split_line()
>>> s.send('1 2 3')
TypeError: can't send non-None value to a just-started generator
>>> s=split_line()
>>> s.send(None)
ready to split

使用產生器與協程

乍看之下,如何使用產生器和協程解決實際問題似乎並不明顯。但在解決系統、網路和分散式運算方面的某些問題時,產生器和協程特別有用。實際上,yield已經成為Python最強大的關鍵字之一。

比如,要建立一個處理檔案的管道:

代碼如下:


import os,sys
def default_next(func):
def start(*args,**kwargs):
f=func(*args,**kwargs)
f.next()
return f
return start
@default_next
def find_files(target):
topdir=yield
while True:
for path,dirname,filelist in os.walk(topdir):
for filename in filelist:
target.send(os.path.join(path,filename))

@default_next
def opener(target):
while True:
name=yield
f=open(name)
target.send(f)

@default_next
def catch(target):
while True:
f=yield
for line in f:
target.send(line)

@default_next
def printer():
while True:
line=yield
print line


然後將這些協程串連起來,就可以建立一個資料流處理管道了:

代碼如下:


finder=find_files(opener(catch(printer())))
finder.send(toppath)


程式的執行完全由將資料發送到第一個協程find_files()中來驅動,協程管道會永遠保持活動狀態,直到它顯式的調用close()。

總之,產生器的功能非常強大。協程可以用於實現某種形式的並發。在某些類型的應用程式中,可以用一個任務調度器和一些產生器或協程實現協作式使用者空間多線程,即greenlet。yield的威力將在協程,協同式多任務處理(cooperative multitasking),以及非同步IO中得到真正的體現。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.