web-dev-qa-db-ja.com

デコレータで変数をスコープに注入する方法は?

[免責事項:やりたいことをもっとPythonで行う方法があるかもしれませんが、ここで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が見つからないのか理解できません。

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

48
beardc

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

あなたが達成することを期待できる最善の方法は、関数のownグローバル名前空間を使用してglobal名前を追加することです:

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を定義することもできます。

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

51
Martijn Pieters

以下は、multiple変数を、関数のスコープに @ Martijn Pieters が彼の答えで行うこととやや似た方法で注入する方法です。 。主にそれがより一般的な解決策であり、notを実行するために複数回適用する必要があるため、私はそれを投稿しています。彼(および他の多く)が答えるのと同じこと。

from functools import wraps

def inject_variables(context):
    """ Decorator factory. """
    def variable_injector(func):
        @wraps(func)
        def decorator(*args, **kwargs):
            try:
                func_globals = func.__globals__  # Python 2.6+
            except AttributeError:
                func_globals = func.func_globals  # Earlier versions.

            saved_values = func_globals.copy()  # Shallow copy of dict.
            func_globals.update(context)

            try:
                result = func(*args, **kwargs)
            finally:
                func_globals = saved_values  # Undo changes.

            return result

        return decorator

    return variable_injector

if __== '__main__':
    namespace = {'a': 5, 'b': 3}

    @inject_variables(namespace)
    def test():
        print('a:', a)
        print('b:', b)

    test()
5
martineau

できません。 Python has lexical scoping。つまり、識別子の意味は、ソースコードを見たときに物理的に囲むスコープのみに基づいて決定されます。

5
newacct

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

「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()
3
M07

Pythonのスコープはレキシカルですので、厄介な副作用を伴わずにあなたが望むことをするためのきれいな方法はないのではないかと思います。デコレータを介して関数に関数を渡すことをお勧めします。

c = 'Message'

def decorator_factory(value):
    def msg_decorator(f):
        def inner_dec(*args, **kwargs):
            res = f(value, *args, **kwargs)
            return res
        inner_dec.__= f.__name__
        inner_dec.__doc__ = f.__doc__
        return inner_dec
    return msg_decorator

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

msg_printer()  # prints 'Message'
3
def merge(d1, d2):
    d = d1.copy()
    d.update(d2)
    return d

# A decorator to inject variables
def valueDecorator(*_args, **_kargs):
    def wrapper(f):
        def wrapper2(*args, **kargs):
            return f(*args, **kargs)
        wrapper2.__= f.__name__
        wrapper2.__doc__ = f.__doc__
        oldVars = getattr(f, 'Vars', [])
        oldNamedVars = getattr(f, 'NamedVars', {})
        wrapper2.Vars = oldVars + list(_args)
        wrapper2.NamedVars = merge(oldNamedVars, _kargs)
        return wrapper2
    return wrapper

@valueDecorator(12, 13, a=2)
@valueDecorator(10, 11, a=1)
def func():
    print(func.Vars)
    print(func.NamedVars)

グローバルスコープを修正する代わりに、注釈付き関数自体を変更する方が合理的です。

1
Martin Wang

次に、デコレータを使用して関数のスコープに変数を追加する簡単なデモを示します。

>>> def add_name(name):
...     def inner(func):
...         # Same as defining name within wrapped
...         # function.
...         func.func_globals['name'] = name
...
...         # Simply returns wrapped function reference.
...         return func
... 
...     return inner
...
>>> @add_name("Bobby")
... def say_hello():
...     print "Hello %s!" % name
...
>>> print say_hello()
Hello Bobby!
>>>
0
tlovely

In python関数がオブジェクトであると仮定すると、次のことができます...

#!/usr/bin/python3


class DecorClass(object):
    def __init__(self, arg1, arg2):
        self.a1 = arg1
        self.a2 = arg2

    def __call__(self, function):
        def wrapped(*args):
            print('inside class decorator >>')
            print('class members: {0}, {1}'.format(self.a1, self.a2))
            print('wrapped function: {}'.format(args))
            function(*args, self.a1, self.a2)
        return wrapped


    @DecorClass(1, 2)
    def my_function(f1, f2, *args):
        print('inside decorated function >>')
        print('decorated function arguments: {0}, {1}'.format(f1, f2))
        print('decorator class args: {}'.format(args))


    if __== '__main__':
        my_function(3, 4)

結果は次のとおりです。

inside class decorator >>
class members: 1, 2
wrapped function: (3, 4)
inside decorated function >>
decorated function arguments: 3, 4
decorator class args: (1, 2)

詳細はこちら http://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html

0
dAn