Python生成器

生成器

通過列表生成式,我們可以直接創(chuàng)建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創(chuàng)建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了。所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環(huán)的過程中不斷推算出后續(xù)的元素呢?這樣就不必創(chuàng)建完整的list,從而節(jié)省大量的空間。在Python中,這種一邊循環(huán)一邊計算的機制,稱為生成器:generator

創(chuàng)建方法1

要創(chuàng)建一個生成器,有很多種方法。第一種方法很簡單,只要把一個列表生成式的 [ ] 改成 ( )

In [1]: L = [ x*2 for x in range(5)]

In [2]: L
Out[2]: [0, 2, 4, 6, 8]

In [3]: G = ( x*2 for x in range(5))

In [4]: G
Out[4]: <generator object <genexpr> at 0x7f626c132db0>

創(chuàng)建 L 和 G 的區(qū)別僅在于最外層的 [ ] 和 ( ) , L 是一個列表,而 G 是一個生成器。我們可以直接打印出L的每一個元素,但我們怎么打印出G的每一個元素呢?如果要一個一個打印出來,可以通過 next() 函數獲得生成器的下一個返回值:

In [5]: next(G)
Out[5]: 0

In [6]: next(G)
Out[6]: 2

In [7]: next(G)
Out[7]: 4

In [8]: next(G)
Out[8]: 6

In [9]: next(G)
Out[9]: 8

In [10]: next(G)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-24-380e167d6934> in <module>()
----> 1 next(G)

StopIteration: 

In [11]:

生成器保存的是算法,每次調用 next(G) ,就計算出 G 的下一個元素的值,直到計算到最后一個元素,沒有更多的元素時,拋出 StopIteration 的異常。當然,這種不斷調用 next() 實在是太變態(tài)了,正確的方法是使用 for 循環(huán),因為生成器也是可迭代對象。所以,我們創(chuàng)建了一個生成器后,基本上永遠不會調用 next() ,而是通過 for 循環(huán)來迭代它,并且不需要關心 StopIteration 異常。

In [12]: G = ( x*2 for x in range(5))

In [13]: for x in G:
   ....:     print(x)
   ....:     
0
2
4
6
8

In [28]:

創(chuàng)建方法2

generator非常強大。如果推算的算法比較復雜,用類似列表生成式的 for 循環(huán)無法實現的時候,還可以用函數來實現。

比如,著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數都可由前兩個數相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契數列用列表生成式寫不出來,但是,用函數把它打印出來卻很容易:

In [28]: def fib(times):
   ....:     n = 0
   ....:     a,b = 0,1
   ....:     while n<times:
   ....:         print(b)
   ....:         a,b = b,a+b
   ....:         n+=1
   ....:     return 'done'
   ....: 

In [29]: fib(5)
1
1
2
3
5
Out[29]: 'done'

仔細觀察,可以看出,fib函數實際上是定義了斐波拉契數列的推算規(guī)則,可以從第一個元素開始,推算出后續(xù)任意的元素,這種邏輯其實非常類似generator。

也就是說,上面的函數和generator僅一步之遙。要把fib函數變成generator,只需要把print(b)改為yield b就可以了:

In [30]: def fib(times):
   ....:     n = 0
   ....:     a,b = 0,1
   ....:     while n<times:
   ....:         yield b
   ....:         a,b = b,a+b
   ....:         n+=1
   ....:     return 'done'
   ....: 

In [31]: F = fib(5)

In [32]: next(F)
Out[32]: 1

In [33]: next(F)
Out[33]: 1

In [34]: next(F)
Out[34]: 2

In [35]: next(F)
Out[35]: 3

In [36]: next(F)
Out[36]: 5

In [37]: next(F)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-37-8c2b02b4361a> in <module>()
----> 1 next(F)

StopIteration: done

在上面fib 的例子,我們在循環(huán)過程中不斷調用 yield ,就會不斷中斷。當然要給循環(huán)設置一個條件來退出循環(huán),不然就會產生一個無限數列出來。同樣的,把函數改成generator后,我們基本上從來不會用 next() 來獲取下一個返回值,而是直接使用 for 循環(huán)來迭代:

In [38]: for n in fib(5):
   ....:     print(n)
   ....:     
1
1
2
3
5

In [39]:

但是用for循環(huán)調用generator時,發(fā)現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:

In [39]: g = fib(5)

In [40]: while True:
   ....:     try:
   ....:         x = next(g)
   ....:         print("value:%d"%x)      
   ....:     except StopIteration as e:
   ....:         print("生成器返回值:%s"%e.value)
   ....:         break
   ....:     
value:1
value:1
value:2
value:3
value:5
生成器返回值:done

In [41]:

next/ _ _ next _ _ /send

例子:執(zhí)行到y(tǒng)ield時,gen函數作用暫時保存,返回i的值;temp接收下次c.send("python"),send發(fā)送過來的值,c.next()等價c.send(None)

In [10]: def gen():
   ....:     i = 0
   ....:     while i<5:
   ....:         temp = yield i
   ....:         print(temp)
   ....:         i+=1
   ....:

使用next函數

In [11]: f = gen()

In [12]: next(f)
Out[12]: 0

In [13]: next(f)
None
Out[13]: 1

In [14]: next(f)
None
Out[14]: 2

In [15]: next(f)
None
Out[15]: 3

In [16]: next(f)
None
Out[16]: 4

In [17]: next(f)
None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-17-468f0afdf1b9> in <module>()
----> 1 next(f)

StopIteration:

使用next()方法

In [18]: f = gen()

In [19]: f.__next__()
Out[19]: 0

In [20]: f.__next__()
None
Out[20]: 1

In [21]: f.__next__()
None
Out[21]: 2

In [22]: f.__next__()
None
Out[22]: 3

In [23]: f.__next__()
None
Out[23]: 4

In [24]: f.__next__()
None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-24-39ec527346a9> in <module>()
----> 1 f.__next__()

StopIteration:

使用send

In [43]: f = gen()

In [44]: f.__next__()
Out[44]: 0

In [45]: f.send('haha')
haha
Out[45]: 1

In [46]: f.__next__()
None
Out[46]: 2

In [47]: f.send('haha')
haha
Out[47]: 3

In [48]:

總結

生成器是這樣一個函數,它記住上一次返回時在函數體中的位置。對生成器函數的第二次(或第 n 次)調用跳轉至該函數中間,而上次調用的所有局部變量都保持不變。

生成器不僅“記住”了它數據狀態(tài);生成器還“記住”了它在流控制構造(在命令式編程中,這種構造不只是數據值)中的位置。

生成器的特點:

節(jié)約內存
迭代到下一次的調用時,所使用的參數都是第一次所保留下的,即是說,在整個所有函數調用的參數都是第一次所調用時保留的,而不是新創(chuàng)建的
我的博客

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Python列表生成式 列表推導式的一般語法 這種語法等價于以下代碼 下面舉一些列表推導式的栗子: Python中...
    So_ProbuING閱讀 1,393評論 0 0
  • 1.什么是生成器 通過列表生成式,我們可以直接創(chuàng)建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創(chuàng)建一...
    一只寫程序的猿閱讀 1,034評論 0 4
  • 列表生成式即List Comprehensions,是Python內置的非常簡單卻強大的可以用來創(chuàng)建list的生成...
    壁花燒年閱讀 220評論 0 0
  • 我們可以通過列表生成式簡單直接地創(chuàng)建一個列表,但是受到內存限制,列表容量肯定是有限的。而且,創(chuàng)建一個包含100萬個...
    PyChina閱讀 2,904評論 0 3
  • 1.生成器(generator)概念 生成器是迭代器,生成器提供了非常方便的自定義迭代器的途徑,在Python中,...
    Pello_Luo閱讀 887評論 0 0

友情鏈接更多精彩內容