Python編程技術:Python生成器詳解generator和yield

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語句處繼續執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章