Python ソースやバイトコードをダイナミックに読み込む

Perlでは"do 'cfg.pl'"とやるとコードが実行され、最後のexpressionの値が帰ってくる。設定ファイルを読み込むのに便利だ。

cfg.pl


my $msg='OH HAI';
{
foo=>"bar",
hoge=>$msg,
}


print Dumper do 'cfg.pl';
$VAR1 = {
'foo' => 'bar',
'hoge' => 'hoy hoy hoy'
};

Pythonで同じことをやろうとすると、まずeval()してみる。ところが、statementがあるとeval()できない。(このpythonのexpressionとstatementの違いの必要性が今一わからない)
一般ソースは「exec」で読み込む。「exec ... in dict」とすると、モジュールスコープの変数がそのdictionaryに反映される。


# ロードするソース。expressionじゃなくてstatement。
src="""
msg="OH HAI"
foo="bar"
"""

def dump(d):
""" 辞書を表示する """
print dict([ (n,v) for n,v in d.items() if not n.startswith('_')])

mod={}
exec src in mod
dump(mod)
# prints {'msg': 'OH HAI', 'foo': 'bar'}

ファイルからソースを読み込む


mod={}
exec file('./cfg.py').read() in mod
dump(mod)
# {'msg': 'OH HAI', 'foo': 'bar', 'numbers': [0, 1, 2]}

このトリックがわかって、調子にのってソースファイルをロードしていたら、凄く変なバグに出会した。 こうやってロードするのはパスにない簡単な設定ファイルぐらいにしたほうが良さそうだ。

バイトコード(.pyc)を読み込む


import sys
import marshal

# 最初の8バイト=マジックナンバーとタイムスタンプを捨てる。
bytecode=file(sys.argv[1]).read()[8:]
code=marshal.loads(bytecode)
mod={}
exec code in mod
print dict([ (n,v) for n,v in mod.items() if not n.startswith('_')])
# => {'msg': 'OH HAI', 'foo': 'bar', 'numbers': [0, 1, 2]}

これは、このオジサン http://en.wikipedia.org/wiki/Alex_Martelli による。http://stackoverflow.com/questions/1830727/how-to-load-compiled-python-modules-from-memory 「duck typing」ってこの人が令名したんだね。