1. 程式人生 > >深入理解python的yield和generator

深入理解python的yield和generator

本文轉自cotyb

前言

沒有用過的東西,沒有深刻理解的東西很難說自己會,而且被別人一問必然破綻百出。雖然之前有接觸過python協程的概念,但是隻是走馬觀花,這兩天的一次交談中,別人問到了協程,頓時語塞,死活想不起來曾經看過的東西,之後突然想到了yield,但為時已晚,只能說概念不清,所以本篇先縷縷python的生成器和yield關鍵字。

什麼是生成器

  • 生成器是一個特殊的程式,可以被用作控制迴圈的迭代行為
  • 生成器類似於返回值為陣列的一個函式,這個函式可以接收引數,可以被呼叫,但是,不同於一般的函式會一次性返回包含了所有數值的陣列,生成器一次只產生一個值,這樣消耗的內粗數量大大減少,而且允許呼叫函式可以很快的開始處理前幾個返回值。因此,生成器看起來像一個函式但是表現的卻像一個迭代器。

python中的生成器

python提供了兩種基本的方式。

  • 生成器函式:也是用def來定義,利用關鍵字yield一次返回一個結果,阻塞,重新開始
  • 生成器表示式:返回一個物件,這個物件只有在需要的時候才產生結果

下面詳細講解。

生成器函式

為什麼叫生成器函式?因為他隨著時間的推移生成了一個數值佇列。一般的函式在執行完畢之後會返回一個值然後退出,但是生成器函式會自動掛起,然後重新拾起繼續執行,他會利用yield關鍵字關起函式,給呼叫者返回一個值,同時保留了當前的足夠多的狀態,可以使函式繼續執行。生成器和迭代協議是密切相關的,可迭代的物件都有一個__next()__成員方法,這個方法要麼返回迭代的下一項,要麼引起異常結束迭代。
為了支援迭代協議,擁有yield

語句的函式被編譯為生成器,這類函式被呼叫時返回一個生成器物件,返回的物件支援迭代介面,即成員方法__next()__繼續從中斷處執行執行。
看下面的例子:

# codes
def create_counter(n): print "create counter" while True: yield n print 'increment n' n += 1 cnt = create_counter(2) print cnt print next(cnt) print next(cnt) # output <generator object create_counter at 0x0000000001D141B0> create counter 2 increment n 3

分析一下這個例子:

  • 在create_counter函式中出現了關鍵字yield,預示著這個函式每次只產生一個結果值,這個函式返回一個生成器(通過第一行輸出可以看出來),用來產生連續的n值
  • 在創造生成器例項的時候,只需要像普通函式一樣呼叫就可以,但是這個呼叫卻不會執行這個函式,這個可以通過輸出看出來
  • next()函式將生成器物件作為自己的引數,在第一次呼叫的時候,他執行了create_counter()函式到yield語句,返回產生的值2
  • 我們重複的呼叫next()函式,每次他都會從上次被掛起的地方開始執行,直到再次遇到了yield關鍵字

為了更加深刻的理解,我們再舉一個例子。

#coding
def cube(n): for i in range(n): yield i ** 3 for i in cube(5): print i #output 0 1 8 27 64

所以從理解函式的角度出發我們可以將yield類比為return,但是功能確實完全不同,在for迴圈中,會自動遵循迭代規則,每次呼叫next()函式,所以上面的結果不難理解。

生成器表示式:

生成器表示式來自於迭代和列表解析的組合,關於列表解析的概念和用法可以參見我之前的部落格,生成器表示式和列表解析類似,但是他使用尖括號而不是方括號括起來的。如下程式碼:

>>> # 列表解析生成列表
>>> [ x ** 3 for x in range(5)] [0, 1, 8, 27, 64] >>> >>> # 生成器表示式 >>> (x ** 3 for x in range(5)) <generator object <genexpr> at 0x000000000315F678> >>> # 兩者之間轉換 >>> list(x ** 3 for x in range(5)) [0, 1, 8, 27, 64]

就操作而言,生成器表如果使用大量的next()函式會顯得十分不方便,for迴圈會自動出發next函式,所以可以按下面方式使用:

>>> for n in (x ** 3 for x in range(5)): print('%s, %s' % (n, n * n)) 0, 0 1, 1 8, 64 27, 729 64, 4096 >>> 

兩者比較

一個迭代既可以被寫成生成器函式,也可以被協程生成器表示式,均支援自動和手動迭代。而且這些生成器只支援一個active迭代,也就是說生成器的迭代器就是生成器本身。

總結

想起了初中時候老師經常說的,眼觀千遍,不如手動一遍。

不向靜中參妙理,縱然穎悟也虛浮 立乎其大 和而不同 古之成大事者,不惟有超世之才,亦必有堅韌不拔之志   分類: python 標籤: python, yield, generator