62

[免責事項:私がやりたいことを行うためのより多くのpythonicな方法があるかもしれませんが、pythonのスコープがここでどのように機能するかを知りたいです]

名前を別の関数のスコープに挿入するようなことを行うデコレータを作成する方法を見つけようとしています(名前がデコレータのスコープ外に漏れないように)。たとえば、var定義されていないという名前の変数を出力する関数がある場合、それが呼び出されるデコレーター内で定義したいと思います。壊れる例を次に示します。

c = 'Message'

def decorator_factory(value):
    def msg_decorator(f):
        def inner_dec(*args, **kwargs):
            var = value
            res = f(*args, **kwargs)
            return res
        return inner_dec
    return msg_decorator

@decorator_factory(c)
def msg_printer():
    print var

msg_printer()

「」を出力したいのですが、次のようになりMessageます。

NameError: global name 'var' is not defined

varトレースバックは、定義されている場所を指しています。

<ipython-input-25-34b84bee70dc> in inner_dec(*args, **kwargs)
      8         def inner_dec(*args, **kwargs):
      9             var = value
---> 10             res = f(*args, **kwargs)
     11             return res
     12         return inner_dec

だから、なぜ見つからないのか理解できませんvar

このようなことをする方法はありますか?

4

11 に答える 11

64

できません。スコープ名 (クロージャ) はコンパイル時に決定され、実行時に追加することはできません。

達成できる最善の方法は、関数の独自のグローバル名前空間を使用して、グローバル名を追加することです。

def decorator_factory(value):
    def msg_decorator(f):
        def inner_dec(*args, **kwargs):
            g = f.__globals__  # use f.func_globals for py < 2.6
            sentinel = object()

            oldvalue = g.get('var', sentinel)
            g['var'] = value

            try:
                res = f(*args, **kwargs)
            finally:
                if oldvalue is sentinel:
                    del g['var']
                else:
                    g['var'] = oldvalue

            return res
        return inner_dec
    return msg_decorator

f.__globals__ラップされた関数のグローバル名前空間であるため、デコレーターが別のモジュールにある場合でも機能します。がすでにグローバルとして定義されている場合varは、新しい値に置き換えられ、関数を呼び出した後、グローバルが復元されます。

これは、割り当てられておらず、周囲のスコープで見つからない関数内の名前が代わりにグローバルとしてマークされるため、機能します。

デモ:

>>> c = 'Message'
>>> @decorator_factory(c)
... def msg_printer():
...     print var
... 
>>> msg_printer()
Message
>>> 'var' in globals()
False

varしかし、装飾する代わりに、グローバル スコープで直接定義することもできます。

グローバルの変更はスレッドセーフではないことに注意してください。同じモジュール内の他の関数への一時的な呼び出しでも、この同じグローバルが引き続き表示されます。

于 2013-07-25T15:28:26.943 に答える
7

グローバル変数を使用せずに、必要なことを行うクリーンな方法があります。ステートレスでスレッド セーフになりたい場合は、選択の余地はありません。

「kwargs」変数を使用します。

c = 'Message'

def decorator_factory(value):
    def msg_decorator(f):
    def inner_dec(*args, **kwargs):
        kwargs["var"] = value
        res = f(*args, **kwargs)
        return res
    return inner_dec
return msg_decorator

@decorator_factory(c)
def msg_printer(*args, **kwargs):
    print kwargs["var"]

msg_printer()
于 2016-11-30T17:44:21.687 に答える
0

グローバルを使用したソリューションで問題が発生しました。

複数の同時リクエストがある場合、グローバルのコンテキストが上書きされることがあります。私はそれは不可能だと思っていましたが、リクエストが迅速でなかった場合、しばらくしてコンテキスト(グローバル)の変更をキャッチしました。より良い解決策は、kwargs を使用して変数を渡すことです。

def is_login(old_fuction):
    def new_function(request, *args, **kwargs):
        secret_token = request.COOKIES.get('secret_token')
        if secret_token:
            items = SomeModel.objects.get(cookie = secret_token)
            if len(items) > 0:
                item = items[0]
                kwargs['current_user'] = item
                return old_fuction(request, *args, **kwargs)
            else:
                return HttpResponse('error')
        return HttpResponse(status=404)
    return new_function

@is_login  
def some_func(request, current_user):
    return HttpResponse(current_user.name)

装飾された各関数に追加のパラメーターを追加する必要があります。

于 2019-11-20T21:57:06.467 に答える