0

これは不可能かもしれませんが、私は次のようなものを実装しようとしています:

a = 0
with before():
  a += 1

do_thing(a) # does thing with a, whose value is now 1
do_thing(a) # does thing with a, whose value is now 2

そのため、その with ステートメント内でブロックを取得し、そのブロックをどこかに保存して、do_thingそのスコープを使用しながら、各関数呼び出しの前に呼び出すことができるものが必要です。

別のオプションは次のようなものです。

@before
def callback():
  a += 1

with ステートメントではなく。どちらのオプションも私には問題ないと思いますが、 with ステートメントが推奨されます。

ここに、私がやりたいことをするはずのものがあると思いますが、実際に試してみるとエラーが発生します。

4

1 に答える 1

1

関数をリストに格納するデコレーターを作成し、別のデコレーターによって別の関数にアタッチすることができます。それはあなたが問題の難しい部分だと思うもののようですが、それは些細なことです:

before_funcs = []
def before(func):
    before_funcs.append(func)
    return func

def attach_befores(func):
    @functools.wraps(func)
    def newfunc(*args, **kwargs):
        for before_func in before_funcs:
            before_func()
        return func(*args, **kwargs)
    return newfunc

だから、今これを行うことができます:

a = 0

@before
def callback():
    global a
    a += 1

@before
def another():
    global a
    a *= 2

@attach_befores
def do_thing(i):
    print(i)

global aそれ以外の場合、関数は有効ではないため、そこが必要であることに注意してください。


そして今、あなたはそれを呼び出すことができます:

do_thing(a)
do_thing(a)
do_thing(a)
do_thing(a)

ただし、それでは望んだ結果が得られません。特に、グローバルを変更しても、実際の関数aに渡される引数は変更されません。do_thingなんで?関数の引数は、関数が呼び出される前に評価されるためです。したがって、a引数がすでに評価された後に再バインドすると、役に立ちません。もちろん、次の呼び出しに渡される引数も変更されます。したがって、出力は次のようになります。

0
2
6
14

関数に渡された引数を変更したいだけなら、グローバルをいじる必要はありません。before関数に引数を変更させ、実際の関数に引数を渡す前に、decorator-applier に各関数に引数を渡させるだけbeforeです。

または、関数で使用されるグローバルを変更する場合は、パラメーターを取得する代わりに、実際にそれらのグローバルを関数に使用させます。

または、代わりに、値をその場で変更したい場合は、リストなどの変更可能なものにしbefore、グローバルを別の値に再バインドするだけでなく、関数が値を変更するようにします。

しかし、あなたが求めているのは、呼び出しフレームに到達し、引数を取得するためにどの式が評価されたかを把握し、それらを強制的に再評価できるデコレーターです。それはばかげています。


あなたが本当にそれをしたいのなら、それを行う唯一の方法は、バイトコードをキャプチャして解釈することsys._getframe(1).f_codeです.

少なくとも CPython 2.7 では、装飾された関数をスタックにプッシュする一連のコード (単純LOAD_NAMEまたはLOAD_NAME典型的なケースですが、必ずしもそうとは限りません)、次に式を評価するための一連のコード、次にCALL_FUNCTION/ CALL_FUNCTION_VAR/etcを取得します。 . そのため、関数をスタックにプッシュした操作が見つかるまで、操作をシミュレートして逆方向に歩くことができます。(これを誰にでもできる方法で行う方法はわかりませんが、実行できるはずです。次に、関数を a でプッシュし、その後のすべての操作を繰り返す (そして値を返す)新しいcodeオブジェクトを作成します)。LOAD_CONST次に、それを呼び出し元とまったく同じ環境でラップし、ラップされた関数を直接呼び出す代わりに、その新しい関数を呼び出してその値を返しますcodefunction

次に例を示します。

def call_do_thing(b):
    global a
    b += a
    return do_thing(a * b)

疑似バイトコードは次のとおりです。

LOAD_FAST b
LOAD_GLOBAL a
INPLACE_ADD
STORE_FAST b
LOAD_GLOBAL do_thing
LOAD_GLOBAL a
LOAD_FAST b
BINARY_MULTIPLY
CALL_FUNCTION 1
RETURN_VALUE

この場合、関数呼び出しを見つけるのは簡単LOAD_GLOBALです。したがって、そこから までのすべての操作を取得し、RETURN_VALUE指定された関数の代わりに呼び出す新しい関数にまとめてa、新しいグローバルで再評価する必要があります。

于 2013-10-16T18:32:00.053 に答える