ジェネレータは値を 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() は主にレガシーコードや特殊なケースで使われます。