1. 程式人生 > >理解yield(生成器)

理解yield(生成器)

任何使用yield關鍵字的函式都稱之為生成器,如:

def count(n):
    while n > 0:
        yield n   #生成值:n
        n -= 1

另外一種說法:生成器就是一個返回迭代器的函式,與普通函式的區別是生成器包含yield語句,更簡單點理解生成器就是一個迭代器。(有關迭代器和生成器的區別可以看文章末尾處補充)

使用yield,可以讓函式生成一個序列,該函式返回的物件型別是”generator”,通過該物件連續呼叫next()方法返回序列值。

c = count(5)
c.next()
>>> 5
c.next()
>>>4

生成器函式只有在呼叫next()方法的時候才開始執行函式裡面的語句,比如:

def count(n):
    print "cunting"
    while n > 0:
        yield n   #生成值:n
        n -= 1

在呼叫count函式時:c=count(5),並不會列印”counting”只有等到呼叫c.next()時才真正執行裡面的語句。每次呼叫next()方法時,count函式會執行到語句yield n處為止,next()的返回值就是生成值n,再次呼叫next()方法時,函式繼續執行yield之後的語句(熟悉Java的朋友肯定知道Thread.yield()方法,作用是暫停當前執行緒的執行,讓其他執行緒執行。python中的協程和yield的有著千絲萬縷的聯絡),如:

def count(n):
    print "cunting"
    while n > 0:
        print 'before yield'
        yield n   #生成值:n
        n -= 1
        print 'after yield'

上述程式碼在第一次呼叫next方法時,並不會列印”after yield”。如果一直呼叫next方法,當執行到沒有可迭代的值後,程式就會報錯:

Traceback (most recent call last): File “”, line 1, in StopIteration 所以一般不會手動的呼叫next方法,而使用for迴圈:

for i in count(5):
    print i,

例項:

用yield生成器模擬Linux中命令:tail -f | grep python?用於查詢監控日誌檔案中出現有python字樣的行。

import time
def tail(f):
    f.seek(0,2)#移動到檔案EOF,參考:[seek](http://docs.python.org/2/library/stdtypes.html?highlight=file#file.seek)
    while True:
        line = f.readline()  #讀取檔案中新的文字行
        if not line:
            time.sleep(0.1)
            continue
        yield line

def grep(lines,searchtext):
    for line in lines:
        if searchtext in line:
            yield line

呼叫:

flog = tail(open('warn.log'))
pylines = grep(flog,'python')
for line in pylines:
    print line,

例項二:

用yield實現斐波那契數列:

def fibonacci():
    a=b=1
    yield a
    yield b
    while True:
        a,b = b,a+b
        yield b

呼叫:

for num in fibonacci():
    if num > 100:
        break
    print num,

yield中return的作用

作為生成器,因為每次迭代就會返回一個值,所以不能顯示的在生成器函式中return 某個值,包括None值也不行,否則會丟擲“SyntaxError”的異常,但是在函式中可以出現單獨的return,那麼久丟擲StopIteration表示終止迭代。 通過固定長度的緩衝區不斷讀檔案,防止一次性讀取出現記憶體溢位的例子:

def read_file(path):
    size = 1024
    with open(path,'r') as f:
        while True:
            block = f.read(SIZE)
            if block:
                yield block
            else:
                return

如果是在函式中return 具體某個值,就直接拋異常了

>>> def test_return():
...      yield 4
...      return 0
...
  File "<stdin>", line 3
SyntaxError: 'return' with argument inside generator

補充:生成器與迭代器的異同

迭代器的類是有一個next()方法和__iter__()的物件,iter()返回物件本身self,所有生成器都是迭代器,但是反過來迭代器不一定是生成器。生成器就如文章開頭的定義。前段時間CPyUG郵件列表對這個問題討論比較激烈,有興趣的可以去看看。與yield有關的一個很重要的概念叫協程,下篇文章將系統的學習下。

本篇文章是改編於自己的原來的部落格http://liuzhijun.iteye.com/blog/1852369,現做了部分修改。


關注公眾號「Python之禪」(id:vttalk)獲取最新文章 python之禪