Pythonのyield(コルーチン編)

はじめに

なんとなく使ってたPythonのyieldについて調べるシリーズ第2弾です。第1弾はジェネレータとしてのyieldを見ました。今回はコルーチンとしてのyieldを見ていきます。

この記事の内容は、Python3.5.2/Ubuntuで試しています。

コルーチンとは

コルーチン自体はPython固有の機能ではなく、他のプログラミング言語にも存在する機能です。
Wikipediaによると

Coroutines are computer program components that generalize subroutines for nonpreemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations.

中断、再開できるサブルーチンの一種という感じです。だとジェネレータと同じかな??という印象ですが、

…they differ in that coroutines can control where execution continues after they yield, while generators cannot…

yieldした後にコントロール出来るという点がジェネレータと違うようです。

Pythonのコルーチン

コルーチンは、Python2.5でPEP342のジェネレータの拡張仕様として実装された機能です。
以下ドキュメントによると

…ジェネレータが出来たのは出力だけでした。いったんジェネレータのコードが呼び出されてイテレータが作られたあとは、新しい情報をそのジェネレータ関数の再開位置に渡す手段はありませんでした…

– What’s New in Python 2.5 PEP 342: ジェネレータの新機能

ということなので、どうやらジェネレータ関数を呼び出した後、新しい情報をその関数に与えるという点がポイントのようです。

では、早速Pythonでコルーチンを書いていきます。

単純なコルーチン

コルーチンの関数定義はこんな感じです。

def simple_coroutine():
... print("-> started.")
... x = yield
... print("-> received:", x)

ポイントは、yieldが代入式右辺に置かれている点です。動かしてみると

>>> my_coro = simple_coroutine()
>>> next(my_coro)
-> started.

関数がyieldで一時停止するところまでは、ジェネレータと同じですが、以下のようにsendメソッドで呼び出し元から値を与えることが可能です。

>>> my_coro.send(10)
->received: 10
...
StopIteration: 

先程のドキュメントの説明の通り、ジェネレータ関数を呼び出した後に新しい情報を関数に与えることができています。

コルーチンを使った実践的な例

先ほどの例で見たとおり、コルーチンは以下の特徴を持っています。

  • 一時停止可能な関数
  • 外から値を与えることが出来る関数

この特徴を活かして、与えた値の平均値をリアルタイムで計算する関数を書いてみます。

>>> def averager():
...     total = 0.0
...     count = 0
...     average = None
...     while True:
...         term = yield average
...         total += term
...         count += 1
...         average = total/count

sendで値を与えていくと与えた値の合計値からその時点の平均値を計算して返します。

>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0

ジェネレータ関数を呼び出した後、新しい情報をその関数に与えることができるというコルーチンの特性により、このような関数を書けます。

参考

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA