2024-09-17|閱讀時間 ‧ 約 23 分鐘

[Python]通過send() 方法向生成器內部傳遞數據

生成器本身是一種只能輸出數據的結構,它不像列表或其他容器可以存儲數據並操作。它是一種(lazy evaluation)輸出數據的結構,生成器僅在需要時生成數據。因此,它對於處理大數據集或無限序列時非常高效。

然而,生成器與列表或其他容器不同的一個特點是,生成器通常只能輸出數據,但在某些情況下,我們希望生成器能夠根據外部的輸入來動態生成新的結果。這就是 send() 方法的作用。

在 Python 中,可以通過生成器的 send() 方法向生成器內部傳遞數據。


生成器的工作機制:

  1. 生成器函數的定義: 生成器函數定義與普通函數類似,但它使用 yield 來返回數據。
  2. yield 暫停與恢復執行: 當生成器函數執行到 yield,它會暫停並返回當前值。下一次調用生成器(通過 next()send())時,會從暫停處繼續執行。
  3. send() 傳送數據: 除了使用 next() 獲取下一個 yield 的值,還可以用 send(value) 將數據發送給生成器。在 yield 表達式處,生成器會接收到 send() 發送的值並進行處理。

示例:

def my_generator():
value = 0
while True:
received = yield value
if received is not None:
value = received
else:
value += 1

gen = my_generator()

# 獲取生成器的初始值
print(next(gen)) # Output: 0

# 透過 send() 傳遞數據給生成器
print(gen.send(10)) # Output: 10

# 不傳送數據,生成器繼續計算
print(next(gen)) # Output: 11

解釋:

  1. next(gen):啟動生成器,返回初始值 0
  2. gen.send(10):將 10 傳送給生成器,接收 yield,此時生成器內部變量 value 被設置為 10
  3. next(gen):當沒有傳送新值時,生成器默認將 value1,所以輸出為 11

常見用途:

  1. 協程:生成器協程允許生成器與外部代碼動態通信。
  2. 事件驅動編程:在多步處理過程中,動態接收和處理輸入。
  3. 數據流處理:生成器可以根據外部輸入即時計算,避免一次性加載所有數據。

「生成器協程」、「事件驅動編程」、「數據流處理」的 生成器簡單範例


1. 協程(生成器協程允許生成器與外部代碼動態通信)

協程是生成器的擴展,它可以讓你動態接收來自外部的數據並進行處理。

def coroutine_example():
print("協程啟動")
while True:
received = yield
print(f"接收到的數據: {received}")

# 啟動生成器協程
gen = coroutine_example()
next(gen) # 啟動協程
gen.send("第一個數據")
gen.send("第二個數據")

解釋:

  • coroutine_example 是一個協程,它通過 yield 暫停,並等待 send() 傳送的數據。每次 send() 傳送後,協程內部會打印接收到的數據。


2. 事件驅動編程(在多步處理過程中,動態接收和處理輸入)

生成器可以用於事件驅動的場景,接收外部事件並根據事件狀態進行操作。

def event_driven_generator():
print('開始接收外部事件')
event_count = 0
while True:
event = yield event_count
if event == "click":
event_count += 1
elif event == "reset":
event_count = 0
print(f"事件數量: {event_count}")

gen = event_driven_generator()
next(gen) # 啟動生成器

# 模擬事件
gen.send("click")
gen.send("click")
gen.send("reset")
gen.send("click")

解釋:

  • event_driven_generator 生成器接收不同的事件,例如「click」或「reset」。每次收到「click」事件時,會遞增計數,收到「reset」時重置計數。


3. 數據流處理(生成器可以根據外部輸入即時計算,避免一次性加載所有數據)

這個範例展示了如何利用生成器進行數據流處理,動態生成數據而不是一次性加載。

def data_stream_processor(data):
print(f'接收資料')
total = 0
for item in data:
total += item
print(f'處理第{item}個資訊 :{total}')
processed_value = yield total
if processed_value is not None:
total = processed_value
print(f'處理接收的資訊 :{total}')

gen = data_stream_processor([1, 2, 3, 4, 5])
print(next(gen)) # 處理第一個數據
print(gen.send(10)) # 更新總和為 10 並繼續
print(next(gen)) # 繼續處理
print(next(gen)) # 繼續處理
print(gen.send(10)) # 更新總和為 10 並繼續

解釋:

  • 接收資料:當你調用這個生成器時,它會接收一個 data 列表作為輸入。
  • total:這是一個累加變數,初始值為 0,用來計算資料的總和。
  • for item in data:生成器逐步處理 data 列表中的每個項目,每次迭代會將當前的項目 item 加到 total 中。
  • yield total:這行代碼會返回目前的 total 值,同時生成器會暫停並等待下一個操作(通過 next()send())。
  • processed_value = yield total:這允許外部使用 send() 方法傳入數據。傳入的數據會賦值給 processed_value。如果 processed_value 不是 None,則生成器會更新 total 的值,這樣可以動態調整累加的結果。

執行步驟

gen = data_stream_processor([1, 2, 3, 4, 5])

這行程式碼會創建生成器對象 gen,並將列表 [1, 2, 3, 4, 5] 傳遞給它。

print(next(gen))  # 處理第一個數據
  • next(gen) 會啟動生成器的執行,並讓它跑到第一個 yield 位置。
  • 它會處理列表中的第一個項目 1,計算總和 total = 1,並暫停在 yield
  • 輸出:
    接收資料
    處理第1個資訊 :1
    1
print(gen.send(10))  # 更新總和為 10 並繼續
  • 這裡調用 send(10),傳入的數值 10 會作為 processed_value,並影響生成器的執行。
  • 生成器接收到 processed_value = 10,所以 total 會更新為 10,然後繼續處理下一個數據(第 2 項 2)。
  • 總和變為 total = 10 + 2 = 12,並暫停在 yield
  • 輸出:
    處理接收的資訊 :10
    處理第2個資訊 :12
    12
print(next(gen))  # 繼續處理
  • 再次使用 next(gen),生成器會從暫停處繼續執行,處理第三個數據 3,總和更新為 total = 12 + 3 = 15
  • 輸出:
    處理第3個資訊 :15
    15
print(next(gen))  # 繼續處理
  • 生成器繼續處理第四個數據 4,總和變為 total = 15 + 4 = 19
  • 輸出:
    處理第4個資訊 :19
    19
print(gen.send(10))  # 更新總和為 10 並繼續
  • 最後,使用 send(10) 再次將 total 更新為 10,然後處理最後一個數據 5,最終總和變為 total = 10 + 5 = 15
  • 輸出:
    處理接收的資訊 :10
    處理第5個資訊 :15
    15

總結:

  • 生成器 data_stream_processor 逐步處理列表中的數據,並且允許通過 send() 動態修改內部狀態(即 total)。
  • 每次 yield 都會暫停生成器的執行,等待外部的操作(next()send()),並根據外部數據更新狀態。


總結:

  • 生成器 data_stream_processor 逐步處理列表中的數據,並且允許通過 send() 動態修改內部狀態(即 total)。
  • 每次 yield 都會暫停生成器的執行,等待外部的操作(next()send()),並根據外部數據更新狀態。





分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.