Pythonのジェネレータを保存したいという話 

はてなブックマーク - Pythonのジェネレータを保存したいという話
Bookmark this on Delicious

Pythonでも所謂ジェネレータを使えます。無限数列を求めたりする例がよく紹介されていますね。

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
def get_fib():
  (a, b) = (0, 1)
  yield a
  yield b
  while True:
    (a, b) = (b, a + b)
    yield b
 
def main():
  fib = get_fib()
  for i in range(10):
    print fib.next()
 
if __name__ == '__main__':
  main()

ジェネレータはnext()を呼ぶ度に新しい値を返す訳ですが、例えば5回next()を呼んでからジェネレータ自身を保存して置いて、後から6回目以降を呼び出したいというのが本エントリでの話です。

要はジェネレータオブジェクトを(デ)シリアライズしたいのですが、少し調べてみると、Pythonの場合(デ)シリアライズにはpickleというモジュールが使われているようです。そこで以下の様な処理をしたいのですが、残念ながらエラーになります。
TypeError: can’t pickle generator objectsと言われるので、pickleオブジェクトがgeneratorには対応していないようです。

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import pickle
 
def get_fib():
  # 省略
 
def main():
  fib = get_fib()
  for i in range(5):
    print fib.next()
  dump_data = pickle.dumps(fib)  # シリアライズしたいがエラーになる。
  fib_restore = pickle.loads(dump_data)
  for i in range(5):
    print fib_restore.next()
 
if __name__ == '__main__':
  main()

諦めかけたのですが、もう少し調べてみるとgenerator_toolsというビンゴなモジュールが見つかりました。このモジュールを使うと以下のようにdumps,loadsでgeneratorの保存と復元が可能になります。

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
from generator_tools import dumps, loads
 
def get_fib():
  (a, b) = (0, 1)
  yield a
  yield b
  while True:
    (a, b) = (b, a + b)
    yield b
 
def main():
  fib = get_fib()
  for i in range(5):
    print fib.next()     # 途中まで呼び出したgeneratorを
  text = dumps(fib)   # シリアライズ後、
  fib_restore = loads(text)  # デシリアライズして再開
  for i in range(5): 
    print fib_restore.next()
 
if __name__ == '__main__':
  main()

実行結果は以下のようになり、見事にジェネレータの保存・復元が出来ていますね!

0
1
1
2
3
5
8
13
21
34

まだ試行錯誤中なのですが、継続の状態を保存する事で結構面白い事が出来るんじゃないかなぁと思っています。その話は次のエントリで。あとgenerator_toolsのソースを読んでみるとpickleオブジェクトをgeneratorに対応させる形で拡張していました。こういう事が自分で出来るようになると一人前なんだろうなと思いますが、中々遠いですね。精進あるのみです。

関連する記事

タグ: ,

コメントをどうぞ