exec,eval とクロージャではまる&遅延評価

[pukiwiki]
Pythonにて遅延評価っぽいデコレータを書こうとして、exec文やevalとクロージャを組み合わせたところはまったのでメモ。
-[[ググる:Python 遅延評価]]
[/pukiwiki]

[pukiwiki]
Python2.5の話なので、他のバージョンについては不明ー
—-
そもそもの動機
[[PythonからswfmillでFlashムービー(swfファイル)を作成してみる:http://boxheadroom.com/2009/04/22/py_swfmill]]
にて、

swf(header(framerate=15),DoAction(“stop”),ShowFrame())

みたいな感じに書けるようにしたかったのですが。。。
上記の場合、swf関数を評価する前に引数が評価されてしまうと、いろいろ都合が悪い。

-引数を先に評価してもいいようなプログラムにする
-引数を遅延評価する

という二つの解決策を考えたのですけれども、まだやったことのない遅延評価っぽいものにチャレンジしてみることに。
[/pukiwiki]
普通に関数を引数にとる関数を書いた場合
(実際には@fdeco みたくデコレータとして使うと思っていただきたい)

def fdeco(f):
    def f2(x):
        print "def"
        return f(x)
    return f2

def fadd(x):
    return x+1
    
f=fdeco(fadd)
print f(1)

fdecoを次のコードに差し替えると動きません
実行しちゃダメ!

def fdeco(f):
    exec """def f2(x):return f(x)"""
    #f2=eval(lambda x,y: f(x,y)")
    #evalにしても動きません
    return f2

これなら動きます
execする関数を二重にくくり、fを引数のデフォルト値として与えています。

def fdeco(f):
    exec """
def f3(f=f):
    def f2(x):
        return f(x)
    return f2
"""
    return f3()

evalに差し替える場合。これも動きます

def fdeco(f):
    return  eval("lambda f=f:lambda x:f(x)")() 

なぜ動かなかったか理解するのに、ちょっと時間がかかりました(汗

・関数の中でevalやexecを行った場合、まだfは定まってないので、生成される関数オブジェクトのクロージャとしては、引数 f は渡されない (らしい)

ということのようです。
結局、遅延評価するデコレータはまだ書けてないわけですが。。。

ジェネレータを作って、あとで評価できるようなデコレータだけメモ。
evalやexec使わずに、関数の引数を動的に変化させる方法をご存知のかたは、教えてくださいぃー

"""
@gen
def f(x) : return x+1

g=f(1)  #generator
g.next  -> 2
"""
import inspect

def lazy(_):
    argname,args,kwargs,defaults=inspect.getargspec(_)
    a=argname[:]
    a2=argname[:]
    if defaults:
        d0=len(a)-len(defaults)
        for i in xrange(len(defaults)):
            j=-i-1
            a[j]=a[j]+"=%s"%defaults[j]
    if args :
        a.append("*%s"%args)
        a2.append("*%s"%args)
    if kwargs :
        a.append("**%s"%kwargs)
        a2.append("**%s"%kwargs)
    a=",".join(a2)        
    a2=",".join(a2)
    txt="""
def _3(_=_):
    def _2(%s):
        while True:
            yield _(%s)
    return _2
"""%(a,a2)
    print txt
    exec txt
    return _3()

@lazy
def f(*args):
    return sum(args)

def force(x) : return x.next()

print "f(1,2,3)"
g=f(1,2,3)
print force(g)

実体はただのジェネレータなので、複数回forceするとエラーになりますけど(汗
何度でも呼び出せるように修正。
2回目以降は、一回目の値をキャッシュして返す。。。のがよいのですが副作用が目的なので、毎回 元の関数を呼び出しております

<<追記>>
Todo : 修飾される関数の__doc__を引き継ぐのを忘れてました。
[pukiwiki]
-[[デコレータを書く時にはfunctools.wrapsを使おう(スコトプリゴニエフスク通信):http://d.hatena.ne.jp/perezvon/20090427/1240838573]]
-[[functools — 高階関数と呼び出し可能オブジェクトの操作:http://www.python.jp/doc/release/lib/module-functools.html]]

メモメモ
[/pukiwiki]

exec,eval とクロージャではまる&遅延評価」への2件のフィードバック

  1. def lazy(_):
    def _2(*args,**kwargs):
    while True:
    yield _(*args,**kwargs)
    return _2

  2. コメントありがとうございます。
    これだとIDLEなどで編集するときコードヒントが出ないので、元関数と同じ引数にするためexecを使っております。

コメントを残す

メールアドレスが公開されることはありません。