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