生成器,是一個用來建立迭代器的工具。它簡單而強大,類似寫函式那樣進行定義,但是需要返回資料時不是使用return
,而是使用yield
語句。
生成器函式
用yield
語句返回資料的“函式”,稱為生成器函式。我們把上一節中自定義類LessThan
改寫成生成器函式:
In [30]: def lessthan(n):
...: for i in range(n-1, -1, -1):
...: yield i
...:
...:
In [31]: for i in lessthan(5):
...: print(i)
...:
4
3
2
1
0
In [32]: lt = lessthan(3)
## 檢視生成器物件的__iter__()和__next__():
In [33]: lt.__iter__?
Signature: lt.__iter__()
Call signature: lt.__iter__(*args, **kwargs)
Type: method-wrapper
String form: <method-wrapper '__iter__' of generator object at 0x7fc048cb8ba0>
Docstring: Implement iter(self).
In [34]: lt.__next__?
Signature: lt.__next__()
Call signature: lt.__next__(*args, **kwargs)
Type: method-wrapper
String form: <method-wrapper '__next__' of generator object at 0x7fc048cb8ba0>
Docstring: Implement next(self).
透過生成器改寫LessThan
類後,程式碼更加簡潔緊湊,因為它自動建立了__iter__()
和__next__()
方法,透過for
迴圈可以遍歷生成器物件。
接下來我們定義一個生成器物件lt
,對這個生成器物件呼叫next()
,每一次呼叫它都會從上次離開的位置回覆執行(也就是記住上次執行語句時的所有資料值)。當生成器生成了所有元素(生成器終結)就會引發StopIteration
錯誤。
In [53]: lt = lessthan(3)
In [54]: next(lt)
Out[54]: 2
In [55]: next(lt)
Out[55]: 1
In [56]: next(lt)
Out[56]: 0
In [57]: next(lt)
---------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-37-00f31299a3f9> in <module>
----> 1 next(lt)
StopIteration:
生成器解析式
為了實現一些簡單的生成器,我們可以不用函式的形式,而是用類似列表解析式的語法,將外層的方括號用圓括號代替即可。
生成器表示式相比完整的生成器更緊湊但較不靈活,相比等效的列表推導式則更為節省記憶體。比如下面的的程式碼,用列表表示式生成的mylist
的每個元素都儲存在記憶體中,而mygener
每次迭代時才會產生一個元素。假設元素個數不是10,而是100萬甚至更多,此時生成器的記憶體優勢會非常明顯。
In [41]: mylist = [i*i for i in range(10)]
In [42]: mylist
Out[42]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
In [43]: mygener = (i*i for i in range(10))
In [44]: mygener
Out[44]: <generator object <genexpr> at 0x7fc048be3bf8>
生成器解析式被設計用於生成器將立即被外層函式所使用的情況,比如:
In [45]: sum(i*i for i in range(10))
Out[45]: 285
sum()
括號裡面的i*i for i in range(10)
就是一個生成器解析式,避免生成一個列表而佔用過多記憶體。
同樣的,下面的例子中都是使用了生成器解析式:
xvec = [10, 20, 30]
yvec = [7, 5, 3]
sum(x*y for x,y in zip(xvec, yvec)) # dot product
from math import pi, sin
sine_table = {x: sin(x*pi/180) for x in range(0, 91)}
unique_words = set(word for line in page for word in line.split())
valedictorian = max((student.gpa, student.name) for student in graduates)
data = 'golf'
list(data[i] for i in range(len(data)-1, -1, -1))
總結
Python提供了兩種方式實現生成器:
(1)生成器函式
語法上與普通函式相似,用yield
替代return
換回值;自動實現迭代器協議:__iter__()
方法和__next__()
方法。沒有值可返回時,引起StopInteration
異常。yield
語句掛起生成器函式的狀態,以便再次迭代時從離開的狀態繼續執行。
(2)生成器解析式
類似列表解析式,用圓括號替換方括號,從而簡單實現簡單的生成器。
(3)生成器的優點
程式碼緊湊,節省記憶體。不像列表可以多次遍歷,生成器只能遍歷一遍。
我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***