4

これは少し注意が必要ですが、簡単に言えば、これが必要です。

関数fooとデコレータがありますprocess

@process
def foo(x, y):
    print x + y

デコレータは、関数とそのパラメータをProcess渡されるオブジェクトを作成していますfoo

# The process decorator
def process(func=None, **options):
    if func != None:
        def _call(*args, **kwargs):
            if len(options) > 0 and 'fail' in options:
                return Process(func, fail=options['fail'], *args, **kwargs)
            else:
                return Process(func, fail=None, *args, **kwargs)
        return _call
    else:
        def _func(func):
            return process(func, **options)
        return _func

foo実行時にプロセスデコレータに与えられる関数を変更して、別の関数、たとえばget_locals()、ローカル変数を上書きする可能性のある関数の呼び出しを次の最初の部分に含めるようにしfooます。

def foo(x, y):
    get_locals()
    print x + y

で何かをしようとしましたinspectが、関数のソースを取得し、後でそれをコードオブジェクトにコンパイルしましたが、これにより'code' object is not callable例外などが発生しました。

これも可能ですか?より簡単でより良い解決策はありますか?

4

1 に答える 1

1

あなたがやりたいことにはいくつかの異なるアプローチがあり、「清潔さ」(追加のハックとは対照的に)、実行のしやすさ(そして後で理解して維持すること)、Python実装への依存などが異なります。

どのアプローチを使用するかを制限する要因は、装飾された関数自体の宣言をどのように妨害できるか、または妨害したいかによって異なります。

1.したがって、デコレータを使用するすべてのコードを変更する(またはデコレータを使用する他のコードに文書化する)立場にある場合は、最も簡単で、単純で、私がお勧めすると思います。 goは、実行時に関数のデフォルト引数タプル(foo.func_defaults)を再割り当てします。

このアプローチでは、実行中のコードで上書きする変数を、デフォルト値の関数パラメーターとして宣言する必要があります。

(文書化の目的で、あなたよりも単純なデコレータを使用します)

new_args = (10,20)

def process(func):
    def new_func(*args, **kw):
        func_defaults = func.func_defaults
        func.func_defaults = new_args
        result = func(*args, **kw)
        func.func_defaults = func_defaults
        return result
    return new_func

@process
def foo(x=1, y =2):
    print x + y

そして、それは「うまくいく」(tm)

>>> foo()
30

2.装飾された関数が特定のパターンに従う必要がある別のアプローチは、上書きするすべての変数をグローバル変数として宣言し、呼び出し時に、すべてが元の関数とまったく同じである新しい関数オブジェクトを作成することです。グローバル辞書を変更します。呼び出しごとに新しい関数オブジェクトを作成しているので、これはスレッドセーフです-0ですが、関数パラメーターとして宣言されている変数を変更することはできません(Pythonはこれを「ローカル」としてマークし、後でマークすることはできません) 「グローバル」として)

from types import FunctionType

new_args = {"x":10, "y":20} 

def process(func):
    def new_func(*args, **kw):
        temp_func = FunctionType(func.func_code,
                                 new_args,
                                 func.func_name, 
                                 func.func_defaults,
                                 func.func_closure)
        return temp_func(*args, **kw)
    return new_func


@process
def foo():
    global x, y
    print x + y

3.同じアプローチに従って、オーバーライドするローカル変数ごとに、デフォルト値を受け取るものとして関数の上部にリストします-のように

def foo(x=1,y=2):
   z= 3
   w = 4

また、実行時に、関数オブジェクトだけでなく、そのコードオブジェクトも呼び出すたびに動的に再構築します。Yuは、元のfunc.func_codeオブジェクトと同じパラメーターを使用してtypes.CodeTypeを呼び出すことができますが、「定数"パラメータ-元のco_consts属性を置き換えます。

実装に依存しすぎるため、「実験的なハックは素晴らしいが、実際の製品コードには恐ろしい」という領域に該当するため、この方法の例は示しません。

4.デコレータで、コードトレースをオンに設定できます。これは通常、デバッガユーティリティによって実行されるため、ターゲットコードで計算される各式に対して指定した関数へのコールバックを取得できます。 「settrace」、およびコールバック関数で、ターゲット関数のローカル変数をいじります。
sys.gettraceのドキュメント(http://docs.python.org/library/sys.html#sys.settrace )を見ると、これは非常に簡単ですが、まったく機能しないという小さな不便があります。 :-)これは、フレームローカルディクショナリで読み取りと書き込みを行う権利はありますが、言語の最適化では、使用時にそのディクショナリから値が常にフェッチされるとは限らないためです(存在する場合)。したがって、この単純な例は、それが機能していないことを示しています。

>>> import sys
>>> def a():
...    b = 1
...    f = sys._getframe()
...    print f.f_locals
...    f.f_locals["b"] = 2
...    print b
... 
>>> a()
{'b': 1, 'f': <frame object at 0x1038cd0>}
1

5.最後に、あなたが言うとおりに実行し、Pythonの「dis」モジュールとコンパイラー関数を使用して、関数コード内(バイトコードに直接)に変数属性を挿入することができます。関数のコードオブジェクトを逆コンパイルし、バイトコードをうまくいじって目的の変数を目的の値で適切にロードし、バイトコードを再コンパイルし、コードオブジェクトを再作成し(前述のtypes.CodeType呼び出しの前に使用)、適切な「定数」値を使用して変数を適切にロードし、そこから関数オブジェクトを再作成して、関数を呼び出します。

このアプローチが他のアプローチよりも役に立たないと思う理由を列挙せず、リファレンス実装も試してみません。ただし、適切に実行されれば、少なくともcpython2.xでは機能します。

于 2012-07-08T19:18:51.847 に答える