3.1 認識生成器
生成器 是返回迭代器物件的函數。這些迭代器會在請求時生成值,讓你可以處理潛在的大型資料集,而不需要將其全部載入記憶體中。
有幾種方式可以創建生成器,下面會介紹其中最受歡迎的。
基於函數的生成器
使用 yield 關鍵字可以創建生成器。在呼叫包含 yield 的函數時,它會返回一個生成器物件,但不會 立即執行函數內的程式碼。相反地,執行會暫停在 yield 表達式處,並在每次呼叫生成器物件的 __next__() 方法時恢復。
def count_up_to(max):
count = 1
while count <= max:
yield count
count += 1
counter = count_up_to(5)
print(next(counter)) # 輸出: 1
print(next(counter)) # 輸出: 2
print(next(counter)) # 輸出: 3
print(next(counter)) # 輸出: 4
print(next(counter)) # 輸出: 5
如果函數中有 yield,那麼 Python 就會創建一個生成器物件來管理函數的執行狀態,而不是傳統地執行函數。
生成器表達式
生成器表達式與列表推導 (List Comprehensions) 類似,但使用圓括號而不是方括號來創建。它們也會返回生成器物件。
squares = (x ** 2 for x in range(10))
print(next(squares)) # 輸出: 0
print(next(squares)) # 輸出: 1
print(next(squares)) # 輸出: 4
你比較喜歡哪種方式呢?
3.2 生成器的優勢
適合記憶體效能
生成器會即時計算值,因此可以在不將整個資料集載入記憶體的情況下處理大型資料集,這讓生成器成為處理大型資料集或資料流的理想選擇。
def large_range(n):
for i in range(n):
yield i
for value in large_range(1000000):
# 逐一處理值
print(value)
懶惰計算
生成器會進行懶惰計算,也就是說,只在需要計算值時才進行計算,這可以避免不必要的計算並提高效能。
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib))
語法的便利性
生成器提供了一個簡便的語法來創建迭代器,這讓代碼的撰寫和閱讀都更加簡便。
3.3 使用生成器
在標準庫中使用生成器的範例
Python 標準庫中的許多函數都使用生成器。例如,函數 range() 返回一個生成器物件,可以生成一個數列。
for i in range(10):
print(i)
是的,這個世界再也不會一樣了。
創建無窮序列
生成器可以創建無窮序列,這在生成無窮資料流的情境下非常有用。
def natural_numbers():
n = 1
while True:
yield n
n += 1
naturals = natural_numbers()
for _ in range(10):
print(next(naturals))
使用 send() 和 close()
生成器物件支援 send() 和 close() 方法,這允許將值發送回生成器並終止其執行。
def echo():
while True:
received = yield
print(received)
e = echo()
next(e) # 啟動生成器
e.send("Hello, world!") # 輸出: Hello, world!
e.close()
3.4 生成器的應用
生成器與例外處理
生成器可以處理例外狀況,這讓它們成為撰寫更具穩定性的代碼的強大工具。
def controlled_execution():
try:
yield "Start"
yield "Working"
except GeneratorExit:
print("Generator closed")
gen = controlled_execution()
print(next(gen)) # 輸出: Start
print(next(gen)) # 輸出: Working
gen.close() # 輸出: Generator closed
我們將在後續講座中介紹例外處理,但我想你們會發現生成器處理例外非常出色。
嵌套生成器
生成器可以是嵌套的,這讓我們能夠創建複雜的迭代結構。
def generator1():
yield from range(3)
yield from "ABC"
for value in generator1():
print(value)
# 輸出
0
1
2
A
B
C
解釋:
yield from: 這個結構用於將部分操作委派給另一個生成器,讓代碼更簡潔且可讀性更好。
生成器與效能
使用生成器可以顯著提高程式的效能,因為它減少了記憶體的使用並更有效地執行迭代。
列表與生成器的對比範例
import time
import sys
def memory_usage(obj):
return sys.getsizeof(obj)
n = 10_000_000
# 使用列表
start_time = time.time()
list_comp = [x ** 2 for x in range(n)]
list_time = time.time() - start_time
list_memory = memory_usage(list_comp)
# 使用生成器
start_time = time.time()
gen_comp = (x ** 2 for x in range(n))
gen_result = sum(gen_comp) # 計算總和以便比較結果
gen_time = time.time() - start_time
gen_memory = memory_usage(gen_comp)
print(f"列表:")
print(f" 時間: {list_time:.2f} 秒")
print(f" 記憶體: {list_memory:,} 位元組")
print(f"\n生成器:")
print(f" 時間: {gen_time:.2f} 秒")
print(f" 記憶體: {gen_memory:,} 位元組")
列表:
時間: 0.62 秒
記憶體: 89,095,160 位元組
生成器:
時間: 1.13 秒
記憶體: 200 位元組
GO TO FULL VERSION