pythonで動的に生成したモジュールをキャッシュする方法
pythonで動的に生成したモジュールをキャッシュする方法
pythonで動的に作ったコードから生成したモジュールをキャッシュする方法について調べたのでまとめました。
- bytecode経由でキャッシュする方法
- tempfile経由でキャッシュする方法
ちなみにこれはテンプレートエンジンを作る時に調べました。前者はjinja2が使っていて、後者はmakoが使っている方法です。
今回は以下の様なコードをキャッシュすることにします(とある記事の答えです)。
def distribute(xs, n): return zip(*zip(*([iter(xs)] * n)))
bytecode経由でキャッシュする方法
こちらはpythonの.pycの形式に従った形でファイルを出力する方法です。.pyc形式に従っているのでimportで読み込めます。 (TODO:後で.pycの形式について解説する)
以下を実行すると「distribute.pyc」というファイルが生成されます。
import time import marshal import imp source = """\ def distribute(xs, n): return zip(*zip(*([iter(xs)] * n))) """ def write_long(f, x): f.write(bytes([x & 0xff, (x >> 8) & 0xff, (x >> 16) & 0xff, (x >> 24) & 0xff])) with open("distribute.pyc", "wb") as wf: timestamp = int(time.time()) size = len(source) # xxx: code = compile(source, "<string>", "exec") wf.write(b'\0\0\0\0') write_long(wf, timestamp) write_long(wf, size) marshal.dump(code, wf) wf.seek(0) wf.write(imp.get_magic())
実際にdistribute.pycを利用してみます。bytecodeを直接生成してあげればimportも可能ということです。 ちなみにjinja2ではこの通りのコードではないです。(jina2.bccacheの中を覗いてみてください)
$ ls | grep -F .pyc distribute.pyc $ python Python 3.3.1 (default, Apr 6 2013, 12:29:05) [GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import distribute >>> list(distribute.distribute(range(10), 3)) [(0, 3, 6), (1, 4, 7), (2, 5, 8)]
tempfile経由でキャッシュする方法
tempfile経由でキャッシュする方法は以下の様な感じです。昨日作った kamo ではこちらを使うことにしました。
import tempfile import os source = '''\ def distribute(xs, n): return zip(*zip(*([iter(xs)] * n))) ''' fd, path = tempfile.mkstemp() print(path) # /var/folders/b7/2rk7xp2d0hb2r21zbzjwxb_m0000gn/T/tmpgpfeaa os.write(fd, source.encode("utf-8"))
実行した時に書き込まれた場所は「/var/folders/b7/2rk7xp2d0hb2r21zbzjwxb_m0000gn/T/tmpgpfeaa」でした。 こちらの場合は読み込む際にimportではなくimportlibの内部のapiを利用します。 (python2.xの場合にはmachineryではなくimp moduleを使った感じになるかもしれません。)
from importlib import machinery target = "/var/folders/b7/2rk7xp2d0hb2r21zbzjwxb_m0000gn/T/tmpgpfeaa" d = machinery.SourceFileLoader("d", target).load_module() print(list(d.distribute(range(10), 3))) # [(0, 3, 6), (1, 4, 7), (2, 5, 8)]