ジェネレータへ値を渡す

呼称: send() メソッドを使用してジェネレータへ値を渡す
目的: yield を式として使用する際の動作を学ぶ
特徴: 特になし
用例: コルーチンの実装の一つ
備考: エキスパート Python プログラミングの第2章「構文ベストプラクティス - クラスの世界」-イテレータとジェネレータ-ジェネレータを参照

Python 2.2 からジェネレータが導入され、Python 2.5 から yield が式になりました。send() メソッドを利用することで外部から値を渡すことが可能になります。これが具体的にどんな時に何の役に立つのか、私にはピンと来ませんが、取りあえず、こういう動作をするということを確認してみました。

#!/usr/bin/env python

import sys

def upper_input(input):
    return input.upper()

def echo_handler():
    while True:
        data = yield upper_input(raw_input())
        yield sys.stdout.write('stdout: %s\n' % data)

def main():
    g = echo_handler()
    r = None
    while True:
        print 'Before send(): ', r
        r = g.send(r)
        print 'After send(): ', r
        print '-' * 20

if __name__ == '__main__':
    main()

実行結果。

$ python generator_send.py 
Before send():  None
abc
After send():  ABC
--------------------
Before send():  ABC
stdout: ABC
After send():  None
--------------------
Before send():  None
t2y
After send():  T2Y
--------------------
Before send():  T2Y
stdout: T2Y
After send():  None
--------------------
Before send():  None
... (入力待ち)

実行の順序と値が分かり易いようにデバッグメッセージを出力しています。
generator.send(value) の説明によると、next() メソッドを呼び出さなくても send() に None を渡すことでジェネレータの実行を再開できるようです。

実行を再開して、ジェネレータの関数内に外部から値を渡します。渡された値は、カレントの yield 式の結果になります。send() メソッドはジェネレータによって生成された次の値を返します。もしくは、ジェネレータが値を生成することなく終了した場合 StopIteration を発生させます。send() メソッドがジェネレータを開始するために呼び出される場合は、引数に None を渡さなければなりません。というのは、値を受け取る yield 式が存在しなくなるからです。

先ず send() に None を渡すことで upper_input(raw_input()) が実行されます。標準入力から "abc" と入力すると g.send(r) が "ABC" を返して1回目のループが終了します。次のループで send() に "ABC" が渡されるので、echo_handler() 内の data に "ABC" が代入されて sys.stdout.write() が "ABC" を出力します。ここで sys.stdout.write() の結果(None)が yield で返されて2回目のループが終了します。再度、send() メソッドの引数に None が渡されるので、ループが標準入力待ちとなり、これが無限に繰り返されます。

こういう機能があると知っておくと、いつか役に立つ日がくるかもしれません。

リファレンス:
PEP 342 -- Coroutines via Enhanced Generators
http://www.python.jp/doc/release/ref/yield.html
Python のジェネレータ (1) - 動作を試す | すぐに忘れる脳みそのためのメモ