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生成器主要目的就是為了讓你的程式碼更省資源,更高效!
參考: