Pythonの式をダイナミックに関数にコンパイルする


# 関数定義の雛形。Trueのexpressionを後で置き換える。
# ここで、string代入してもいいが、ASTのノードを置き換えた方が安全そうな気がする。
>>> defun="""
def foo(num):
return True
"""
... ... ... >>>
# ASTにパースする
>>> x=ast.parse(defun, mode='single')
>>> x
<_ast.Interactive object at 0x7f963f9c1950>

>>> expression='num==42'

# ASTでTrueにあたるノードを上の式に置き換える。
# body,valueのコンビネーションは試行錯誤でみつける。
# ASTをダンプする機能があったら簡単に見付かるだろう。
>>> x.body[0].body[0].value=ast.parse(expression, mode='single').body[0].value
>>> c=compile(x, '', 'single')
# コンパイルするとコードになる。Pythonらしく素直だ。
>>> c
<code object <module> at 0x7f963f96b3f0, file "<string>", line 2>
>>> locals={}
# 関数定義をサンドボックス内で実行。
>>> eval(c, {}, locals)
>>> locals
{'foo': }
# 関数を現スコープに「インポート」
>>> foo=locals['foo']
>>> foo(42)
True
>>> foo(43)
False
>>>

これで、ランタイムに文字列として得た式を効率よく何度も実行できることになる。例えばコマンドでデータに対する処理をアドホックな言語をでなくpythonの式を引数として与えることができるようになる。
cat life.jsons | datastream filter 'meaning==42'
という具合に。

追加: 関数としてまとめてみた。
python式をコンパイルして関数にする関数(なんかややこしいな…)

def function(body):
"""python式を関数にコンパイルする"""

# 関数ソース
defun_src="def surrogate(*args, **kw): return True\n"

# astにパース
defun_ast=ast.parse(defun_src, mode='single')

# コード差し替え
defun_ast.body[0].body=body_ast.body

# コンパイル
defun_code=compile(defun_ast, '', 'single')
# 抽出
locals={}
eval(defun_code, {}, locals)
return locals['surrogate']

これだとexpressionを関数にするのに一々returnを入れなければいけない。returnをオプショナルにしたい…