そうだ! EuroPython 2011へ行こう

#4 アプリケーションに関する講演,LT,PSFメンバーミーティング,まとめ

この記事を読むのに必要な時間:およそ 8 分

前回はCPythonの講演PyPyの講演を紹介しました。今回は,Pythonで開発されたフレームワークやライブラリといったアプリケーションに関する講演を紹介します。

Application:アプリケーション

Beyond Python Enhanced Generators:Python拡張ジェネレーターの先へ

動的にシーケンスを生成するものとしてジェネレーターがあります。本講演を紹介する前にPythonのジェネレーターについて簡単に紹介します。ジェネレーターは,関数を定義してreturnの代わりにyieldというキーワードを使うことで作成できます。

>>> def f():
...   for i in range(3):
...     yield i

>>> g = f()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

forループの中でyield文が実行される毎に,この関数の処理が一時停止して呼び出し元に値が返されます。呼び出し元ではnext()メソッドを実行することで, ジェネレーター関数を再開しyieldされた値を受け取れます。

ジェネレーターを任意の場所で終了させるときは,ジェネレーター関数内で明示的にStopIterationを発生させます。また,このサンプルのように関数の終了時に自動的にStopIterationが発生します。

yield文は,Python 2.2から利用できるようになりましたが,その後,PEP 342によりyield文はyield式として再定義されました。

>>> def f():
...   msg = yield "send me message"
...   print msg
...   yield

>>> g = f()
>>> g.next()
'send me message'
>>> g.send("Hello")
Hello

send()というメソッドでジェネレーター内へメッセージを送り,ジェネレーターの処理を再開させます。この仕組みを応用したデータストリーミングライブラリがWeightlessです。

本講演はWeightlessの開発者でもあるErik Groeneveld氏によって行われました。なお,本講演はBEYOND PYTHON ENHANCED GENERATORSスライドから視聴できます。

Erik Groeneveld氏

Erik Groeneveld氏

Weightless

Weightlessは,HTTPのようなプロトコルスタックを含め,Pythonでコルーチンを実装するためのライブラリで次の3つのコンポーネントからなります。

Weightlessは,ジェネレータが次々にジェネレーターを呼び出して処理を委譲するモデルをとります。講演の中ではcomposeモジュールで,サブジェネレーターに処理を委譲するサンプルコードが紹介されました。

Weightlessモデル

Weightlessモデル

トランポリンモデル

一方でWeightlessのモデルと対比してトランポリンモデルも紹介されていました。筆者はトランポリン(trampoline)という言葉は聞いたことがあるものの,具体的にどういったものかが理解できていなかったので参考になりました。

トランポリンモデル

トランポリンモデル

トランポリンモデルを表す次のサンプルコードが紹介されていました。

def demo_coroutine():
  """coroutine a la COBOL"""
  def coroutine_a(n = 0):
    while n < 10:
      n = yield b, n + 1
      print ">", n
  def coroutine_b(n = 0):
    while n < 10:
      n = yield a, n + 1
      print "<", n
  a = coroutine_a()
  b = coroutine_b()
  a.next()
  b.next()
  g = (a, 0)
  while True: # トランポリン
    try:
      g = g[0].send(g[1])
    except:
      break
>>> demo_coroutine()
> 0
< 1
> 2
< 3
...

coroutine_acoroutine_bを,coroutine_bcoroutine_aを交互に呼び出します。この2つのジェネレーターを交互に呼び出す制御部にあたるのがwhileループの部分です。

while True:  # トランポリン
  try:
    g = g[0].send(g[1])
  except:
    break

g = g[0].send(g[1])はそれぞれのジェネレーターを交互に呼び出します。a.send(0)を実行するとcoroutine_aではn = 0がセットされ,print “>”, nが呼び出され,ループの先頭に戻ってyield b, (n + 1)によりcoroutine_aが一時停止します。呼び出し元ではg = (b, 1)が返されて,次のループでb.send(1)が実行されます。それからb.send(1)を実行すると,先ほどと同様にyield a, (n + 1)coroutine_bが一時停止して,g = (a, 2)が返されます。

このように,ある関数を呼び出すと次に呼び出す関数が得られて,次々に繰り返し関数を呼び出す仕組みのことをトランポリン(trampoline)と呼ぶそうです。

composeとPEP 380

composeモジュールを使ってジェネレータの処理をサブジェネレーターへ委譲します。Pythonのジェネレーターは,その直前の呼び出し元に対してのみyieldするという制約があります。この制約により,ジェネレーターの処理をサブジェネレーターに分解しようとした場合,その仕組みが複雑になります。

実際に簡単なサンプルコードを書いてみます。

>>> def f():
...   yield "Hello"
...   msg = yield
...   yield msg

>>> def g():
...   yield f()

>>> _r = g()
>>> _r
<generator object g at 0x10078ca00>

>>> r = _r.next()
>>> r
<generator object f at 0x10078c550>
>>> r.next()
'Hello'
>>> r.next()
>>> r.send("World")
'World'

composeを使うと,この処理は次のように書けます。

>>> from weightless.core import compose
>>> r = compose(g())
>>> r
<generator object _compose at 0x10078c910>
>>> r.next()
'Hello'
>>> r.next()
>>> r.send("World")
'World'

g()というジェネレーターが返すサブジェネレーターを意識せずに操作できます。委譲の考え方から,呼び出し元はg()ジェネレーターが何をするかを知る必要はないのでcomposeがサブジェネレーターを適切に扱ってくれると,呼び出し元とg()ジェネレーターとの結合度が低くなります。

そしてcomposeはデコレーターとして使うこともできます。

@compose
def g():
  yield f()

また,PEP 380により,Python 3.3で導入予定のyield from式を使って次のように記述できるようになる予定です。

def g():
  yield from f()

composeyield fromとよく似た概念であり,さらに少し拡張したものだそうです。

著者プロフィール

森本哲也(もりもとてつや)

一介のプログラマ。

自分で設計して,自分で開発して,自分で直せるような独立したプログラマを目指している。OSSコミュニティのゆるい人のつながりが性にあっていてPythonプログラミングが好き。共訳書に『エキスパート Pythonプログラミング』(アスキーメディアワークス)がある。

Twitter:@t2y

ブログ:http://d.hatena.ne.jp/t2y-1979/

コメント

コメントの記入