[免責事項:やりたいことをもっと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
が見つからないのか理解できません。
このようなことをする方法はありますか?
できません。有効範囲名(クロージャ)はコンパイル時に決定され、実行時に追加することはできません。
あなたが達成することを期待できる最善の方法は、関数の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
を定義することもできます。
グローバルの変更はスレッドセーフではなく、同じモジュール内の他の関数への一時的な呼び出しでも、この同じグローバルが表示されることに注意してください。
以下は、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()
できません。 Python has lexical scoping。つまり、識別子の意味は、ソースコードを見たときに物理的に囲むスコープのみに基づいて決定されます。
グローバル変数を使用せずに、必要なことを行うクリーンな方法があります。ステートレスでスレッドセーフにしたい場合は、選択の余地はありません。
「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()
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'
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)
グローバルスコープを修正する代わりに、注釈付き関数自体を変更する方が合理的です。
次に、デコレータを使用して関数のスコープに変数を追加する簡単なデモを示します。
>>> 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!
>>>
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