Python編程技術:Python生成器詳解generator和yield
什麼是生成器?
通過列表生成式,我們可以直接創建一個列表,但是,受到內存限制,列表容量肯定是有限的,而且創建一個包含100萬個元素的列表,不僅佔用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間,在Python中,這種一邊循環一邊計算的機制,稱爲生成器:generator
生成器是一個特殊的程序,可以被用作控制循環的迭代行爲,python中生成器是迭代器的一種
,使用yield
返回值函數,每次調用yield會暫停
,而可以使用next()函數和send()函數恢復生成器。
生成器類似於返回值爲數組的一個函數,這個函數可以接受參數,可以被調用,但是,不同於一般的函數會一次性返回包括了所有數值的數組,生成器一次只能產生一個值
,這樣消耗的內存數量將大大減小,而且允許調用函數可以很快的處理前幾個返回值,因此生成器看起來像是一個函數,但是表現得卻像是迭代器
生成器generator和yield基礎
generator使用場景:
1. 當我們需要一個公用的,按需生成的數據
2.某個事情執行一部分,另一部分在某個事件發生後再執行下一部分,實現異步。
注意事項:
1. yield from generator_obj 本質上類似於 for item in generator_obj: yield item
2.generator函數中允許使用return,但是return 後不允許有返回值
generator有以下特點:
遵循迭代器(iterator)協議
,迭代器協議需要實現__iter__、next接口- 能過多次進入、多次返回,能夠暫停函數體中代碼的執行
在python的函數(function)定義中,只要出現了yield表達式(Yield expression),那麼事實上定義的是一個generator function
, 調用這個generator function返回值是一個generator。這根普通的函數調用有所區別。
def gen_generator():
yield 1
def gen_value():
return 1
ret = gen_generator()
print (ret, type(ret)) #<generator object gen_generator at 0x02645648> <type 'generator'>
ret = gen_value()
print (ret, type(ret)) # 1 <type 'int'>
從上面的代碼可以看出,gen_generator函數返回的是一個generator實例.
通過以下代碼理解generator和yield和使用方法:
def gen_example():
print ('before any yield')
yield 'first yield'
print ('between yields')
yield 'second yield'
print ('no yield anymore')
gen = gen_example()
gen.next() # 第一次調用next
#before any yield
#'first yield'
gen.next() # 第二次調用next
#between yields
#'second yield'
gen.next() # 第三次調用next
#no yield anymore
#Traceback (most recent call last):
#File "<stdin>", line 1, in <module>
#StopIteratio
調用gen example方法並沒有輸出任何內容,說明函數體的代碼尚未開始執行。當調用generator的next方法,generator會執行到yield 表達式處,返回yield表達式的內容,然後暫停(掛起)在這個地方
,所以第一次調用next打印第一句並返回“first yield”。 暫停意味着方法的局部變量,指針信息,運行環境都保存起來,直到下一次調用next方法恢復。第二次調用next之後就暫停在最後一個yield,再次調用next()方法,則會拋出StopIteration異常。
因爲for語句能自動捕獲StopIteration異常,所以generator(本質上是任何iterator)較爲常用的方法是在循環中使用:
def generator_example():
yield 1
yield 2
for e in generator_example():
print (e)
generator function產生的generator與普通的function區別
:
(1)function每次都是從第一行開始運行,而generator從上一次yield開始的地方運行
(2)function調用一次返回一個(一組)值,而generator可以多次返回
(3)function可以被無數次重複調用,而一個generator實例在yield最後一個值 或者return之後就不能繼續調用了
generator創建
第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator:
>>> mylist = [ x for x in range(1, 10)]
>>> mylist
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> gen = (x for x in range(1,10))
>>> gen
<generator object <genexpr> at 0x7f1d7fd0f5a0>
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
3
...
>>> gen.next()
9
>>> gen.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
其實我們可以使用for循環來代替next()方式, 這樣才更符合高效的編程思路:
>>> gen = ( x for x in range(1, 10))
>>> for num in gen:
... print num
第二種方法是前面演示過的,跟yield配合使用。
我們以斐波拉契數列爲例:
著名的斐波拉契數列(1,1,2,3,5,8,13,21……除了第一個和第二個數外,任意一個數都是由其前兩個數相加的和)
def fib(max):
n,a,b = 0,0,1
while n < max:
print (b)
a,b = b,a+b#相當於將一個tuple(b,a+b)賦值給a,b
n = n + 1
return
fib(6)結果:1 1 2 3 5 8
改成generator方式:其實,上述fib()和generator非常相近了。只需要把print(b)變成yield b 就可以了
def fib(max):
n,a,b = 0,0,1
while n < max:
yield b
a,b = b,a+b#相當於將一個tuple(b,a+b)賦值給a,b
n = n + 1
return
m = fib(6)
for i in m :
print(i)
fib(6)結果:1 1 2 3 5 8
這就是定義generator的第二種方法。如果一個函數中包含yield關鍵字,那麼這個函數就不再是普通函數,而是一個generator。兩者的執行流程可以這麼區別:普通函數是順序執行,遇到return或者最後一行代碼函數就會返回。而generator,在每次調用next()的時候執行,遇到yield語句返回。再次執行的時候,從上次返回的yield語句處繼續執行。