生成器

Python SELF TW
等級 13 , 課堂 4
開放

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 位元組
1
問卷/小測驗
系統函式,等級 13,課堂 4
未開放
系統函式
系統函式
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION