『無為則無心』Python基礎 — 63、Python中的生成器
- 2022 年 3 月 3 日
- 筆記
- 高級測試技能 - Python基礎
1、為什麼要有生成器
Python在數據科學領域可以說是很火,我想有一部分的功勞就是它的生成器了吧。
我們知道我們可以用列表儲存數據,可是當我們的數據特別大的時候,列表中的數據都是放在記憶體中,受到記憶體限制,列表容量肯定是有限的,而且還會降低電腦的性能。
如果僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。但如果列表中元素是按照某種演算法推算出來,那我們就可以在循環的過程中不斷推算出後續的元素,這樣就不必創建完整的列表數據,從而節省大量的空間。
換句話說,我又想要得到龐大的數據,又想讓它佔用空間少,這時生成器就派上用場了,它可以說是一個不怎麼占電腦資源的一種方法。
2、創建生成器
(1)簡單創建生成器
將一個列表推導式(也叫列表生成式) []
改為 ()
即可創建一個生成器。
# 1.用推導式定義一個列表
# 關於推導式請看以前的文章有講解。
my_list = [x * x for x in range(10)]
# 列印列表
print(my_list)
# 查看my_list類型,是一個列表
print(type(my_list))
"""
輸出結果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<class 'list'>
"""
# 2.創建一個生成器
my_gen = (x * x for x in range(10))
# 列印生成器,是一個生成器對象
print(my_gen)
# 查看my_gen對象類型,是生成器類型
print(type(my_gen))
"""
輸出結果:
<generator object <genexpr> at 0x0000000002575148>
<class 'generator'>
"""
(2)生成器的使用
# 創建生成器
my_gen = (x * x for x in range(10))
# 1。方式一:遍歷生成器,使用next方法
print(my_gen.__next__()) # 0
print(my_gen.__next__()) # 1
print(my_gen.__next__()) # 4
print(my_gen.__next__()) # 9
# 或者
print(next(my_gen)) # 16
print(next(my_gen)) # 沒有數據了則會拋出異常StopIteration
# 2.方式二:遍歷生成器的內容
for i in my_gen:
print(1)
# 3.方式三:遍歷生成器的內容
while True:
try:
# 調用next函數,獲取下一個字元
result = next(my_gen)
print(result)
except StopIteration:
# 釋放對it的引用,即廢棄迭代器對象
del my_gen
# 不推出循環會成為私循環
break
提示:
- 在上邊練習中,可以看到和迭代器的用法差不多,在這裡說明一下生成器本身就是一個迭代器。如果有對迭代器不清楚的可以查看前面說明迭代器的文章。
- 上面方式一不斷調用
next()
方法回去元素,實在是太變態了,正確的方法是使用for
循環。generator
保存的是演算法,每次調用next()
,就計算出下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration
的錯誤。
總結:
- 用
[]
推導出來的是迭代器(Iterables
)。- 用
()
推導出來的是生成器(Generators
)。
3、yield
關鍵詞
(1)yield
關鍵詞說明
如果我們想定義一個自己的生成器函數怎麼辦?
Python有yield
關鍵詞。其作用和return
的功能差不多,就是返回一個值給調用者,只不過有yield
的函數返回值後,函數依然保持調用yield
時的狀態,當下次調用的時候,在原先的基礎上繼續執行程式碼,直到遇到下一個yield
或者滿足結束條件結束函數為止。
啥意思?啥意思?啥意思?
- 你先把
yield
關鍵字直觀的看做return
關鍵字,它首先是return
的功能,就是在函數或方法中返回某個值,返回之後程式就不再往下運行了。 yield
相當於返回一個值給調用者,停止執行函數中的語句,並且記住這個返回的位置。下次迭代時(或者執行next
方法的時候),程式碼從yield
記錄位置的下一條語句開始執行。- 帶有
yield
的函數不再是一個普通函數,而是一個生成器generator
。 - 調用一個生成器函數,返回的是一個迭代器對象。
示例:
# 定義一個生成器函數
def testYield():
yield 1
yield 2
yield 3
# 獲得一個生成器對象
ty = testYield()
"""
調用過程:
next(ty)相當於ty.__next__()
掉調用一次next(ty)時
就會執行testYield()內的方法。
當執行的第一行, yield 1時,
返回當前yield的值1給調用者,停止向下執行,並記錄函數中當前的執行位置。
也就是每次遇到 yield 時函數會暫停並保存當前所有的運行資訊,返回 yield 的值。
程式執行結束
當下次再調用next(ty)的時候,
還是會執行testYield()內的方法,
只不過是從yield 1下面一句開始執行。
以此類推。
"""
print(next(ty)) # 1
print(next(ty)) # 2
print(next(ty)) # 3
print(next(ty)) # StopIteration
注意:每次調用
testYield()
函數都會生成一個新的 generator 實例,各實例互不影響。
(2)send()
方法說明
send()
方法和next()
方法一樣,都能讓生成器繼續往下走一步(下次遇到yield停),但send()
能傳一個值,這個值作為yield表達式整體的結果。
def testYield():
yield 1
y = yield 2
if (y == 'hello'):
yield 9
yield 3
ty = testYield()
print(ty.__next__()) # 1
print(next(ty)) # 2
"""
第三次執行,send方法會把"hello"傳遞進去
就是y = "hello"
換句話說,就是send可以強行修改上一個yield表達式值
程式會從第二個yield的下一行開始執行
執行到下一個yield停止,並記錄位置,返回結果。
"""
print(ty.send("hello")) # 9
print(next(ty)) # 3
print(next(ty)) # StopIteration
注意:第一次執行要麼
next(ty)
要麼ty.send(None)
,不能使用ty.send('xxxxx')
,否則會報錯的。
4、使用yield
實現斐波那契數列
"""
數學中有個著名的斐波那契數列(Fibonacci),
數列中第⼀個數0,第⼆個數1,其後的每⼀個數都可由前兩個數相加得到:
如下:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
現在我們想要通過for...in...循環來遍歷迭代斐波那契數列中的前n個數。
那麼這個斐波那契數列我們就可以⽤生成來實現,
每次迭代都通過數學計算來⽣成下⼀個數。
"""
from collections.abc import Iterable, Iterator
class FibGenerator(object):
"""
fib數列生成器
"""
# 初始化方法
def __init__(self):
# 斐波拉契數列中的前兩個數
self.num1 = 0
self.num2 = 1
# 用來記錄迭代次數(計數器)
self.i = 0
def gen(self, count):
# 用來保存迭代的總次數
self.count = count
# 判斷是否迭代結束,如果沒有到達迭代次數,則返回數據
# self.count 需要迭代的次數
# self.i已迭代次數
while self.i < self.count:
yield self.num2
# 計算num1, num2的值,方便下次迭代返回
# 這裡運用的是序列的封包與解包,不會的可以看我以前的文章(元組)
self.num1, self.num2 = self.num2, self.num1 + self.num2
# 執行一次next方法,計數器+1
self.i = self.i + 1
# 創建一個對象
fibGen = FibGenerator()
# 調用生成器函數得到一個生成器
fg = fibGen.gen(15)
# fibIter對象是一個迭代器
print(isinstance(fg, Iterable)) # True
print(isinstance(fg, Iterator)) # True
# next方法方式獲取數據
# print(next(fg)) # 1
# print(next(fg)) # 1
# print(next(fg)) # 2
# print(next(fg)) # 3
# print(next(fg)) # 5
# print(next(fg)) # 8
# 遍歷生成器,可執行
for li in fg:
print(li)
5、總結
- 生成器
generator
就是迭代器iterator
的一種,以更優雅的方式實現的iterator
,而且完全可以像使用iterator
一樣使用generator
。 - 當然除了定義,定義一個
iterator
,你需要分別實現__iter__()
方法方法和__next__()
方法。但generator只需要一個yield關鍵字就可以。 - Python生成器主要目的就是為了讓你的程式碼更省資源,更高效!
參考: