Python の send() でジェネレータに値を送る
ジェネレータは値を yield で返すだけでなく、send() メソッドを使って外部から値を受け取ることもできます。これにより、ジェネレータと呼び出し側で双方向のコミュニケーションが可能になります。
send() の基本
send() を使うと、yield 式の結果として値を渡せます。
def receiver():
while True:
received = yield
print(f"受け取った: {received}")
gen = receiver()
next(gen) # 最初の yield まで進める
gen.send("Hello") # 受け取った: Hello
gen.send("World") # 受け取った: World
最初に next() を呼び出すのは、ジェネレータを最初の yield まで進めるためです。これを「プライミング」と呼びます。
yield 式の値
yield は式として値を持ちます。send() で送った値が yield の評価結果になります。
def echo():
result = None
while True:
received = yield result
result = f"echo: {received}"
gen = echo()
print(next(gen)) # None(初期値)
print(gen.send("A")) # echo: A
print(gen.send("B")) # echo: B
next() と send(None) の関係
next(gen) は gen.send(None) と同じです。
def simple():
value = yield 1
print(f"value = {value}")
yield 2
gen = simple()
print(next(gen)) # 1
print(gen.send(None)) # value = None → 2
実用例:移動平均の計算
send() を使って、リアルタイムで移動平均を計算するジェネレータを作れます。
def moving_average(window_size):
values = []
average = None
while True:
value = yield average
values.append(value)
if len(values) > window_size:
values.pop(0)
average = sum(values) / len(values)
ma = moving_average(3)
next(ma) # プライミング
print(ma.send(10)) # 10.0
print(ma.send(20)) # 15.0
print(ma.send(30)) # 20.0
print(ma.send(40)) # 30.0(10が除外される)
プライミングを自動化するデコレータ
毎回 next() を呼ぶのは面倒なので、デコレータで自動化できます。
def coroutine(func):
def wrapper(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen) # 自動でプライミング
return gen
return wrapper
@coroutine
def receiver():
while True:
received = yield
print(f"受け取った: {received}")
gen = receiver() # next() 不要
gen.send("Hello")
throw() と close()
ジェネレータに例外を送ったり、終了させたりすることもできます。
def careful_gen():
try:
while True:
value = yield
print(f"処理: {value}")
except GeneratorExit:
print("ジェネレータ終了")
except ValueError as e:
print(f"エラー: {e}")
gen = careful_gen()
next(gen)
gen.send("A") # 処理: A
gen.throw(ValueError, "不正な値") # エラー: 不正な値
gen = careful_gen()
next(gen)
gen.close() # ジェネレータ終了
コルーチンとしての活用
send() を活用すると、ジェネレータをコルーチン(協調的なタスク)として使えます。ただし、Python 3.5以降では async/await 構文が推奨されています。send() は主にレガシーコードや特殊なケースで使われます。