0

関数変数のスコープと戻り値についてかなり奇妙な質問があります。呼び出された関数が値を返した後、呼び出し元で呼び出された関数のスコープを検査する方法はありますか?

使用例は単純です。Flask ビューでlocals()、テンプレートにバイパスしたいと考えています。慣習によって必要なテンプレートを定義できますが、すべてのビューで辞書を返すのは面倒です。

4

1 に答える 1

2

関数が戻った後、そのスコープは存在しなくなります。これにより、関数が戻ったにスコープを取得することが困難になります。

ただし、Python のトレースまたはプロファイル機能を使用すると、関数が返される直前にコードを実行しその時点でスタック フレームからローカルを抽出することができます。その後、これらをどこかに追い出し、ラッパー関数を使用して、呼び出された関数の戻り値とともに (またはその代わりに) 返すことができます。

この悪質な目的に使用できるデコレータを次に示します。この実装は恐ろしいハックであり、単なる便宜のために使用するのは悪いスタイルになることに注意してください。おそらく正当な用途を考えられるでしょう...数日待ってください。また、CPython 以外の実装では動作しない可能性があります (実際には動作しない可能性があります)。

import sys, functools

def givelocals(func):
    
    localsdict = {}

    def profilefunc(frame, event, arg):
        if event == "call":
            localsdict.clear()
        elif event == "return":
            localsdict.update(frame.f_locals)
        return profilefunc    

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        oldprofilefunc = sys.getprofile()
        sys.setprofile(profilefunc)
        try:
            return func(*args, **kwargs), dict(localsdict)
        except Exception as e:
            e.locals = dict(localsdict)
            raise
        finally:
            sys.setprofile(oldprofilefunc)

    return wrapper

例:

@givelocals
def foo(x, y):
    a = x + y
    return x * y

>>> foo(3, 4)
(12, {'y': 4, 'x': 3, 'a': 7})

使用したい任意の関数があり、自分が作成したモジュールではないためにデコレートできない場合は、その場でラッパーを作成して呼び出すことができます。

def foo(x, y):
    a = x + y
    return x * y

>>> givelocals(foo)(3, 4)
(12, {'y': 4, 'x': 3, 'a': 7})

または、ラッパーを保存して後で呼び出します。

locals_foo = givelocals(foo)
>>> locals_foo(3, 4)
(12, {'y': 4, 'x': 3, 'a': 7})

ラッパーは、実際の戻り値とローカル ディクショナリのタプルを返します。例外が発生した場合、.locals例外オブジェクトの属性はローカル dict に設定されます。

通常、Python は、関数が戻るときに、ローカル変数によって使用されたメモリを解放することに注意してください。これらの値を保持すると、(それらへの参照がある限り) プログラムがより多くのメモリを使用することになるため、それらが不要になったらクリーンアップすることをお勧めします。

最後に 1 つ: ここでは Python のプロファイル機能を使用しています。これは、関数の呼び出し時と戻り時にのみ呼び出されるためです。2.6 より前のバージョンの Python を使用している場合、プロファイリングがないため、代わりにトレースを使用する必要があります。プロファイル関数は、書かれているようにトレース関数としても機能します。対応するプロファイル関連の関数ではなく、gettrace()andを使用するだけで済みます。settrace()ただし、トレースは行ごとに呼び出されるため、ラップされた関数は著しく遅くなる可能性があります。

于 2013-01-02T15:46:21.227 に答える