Python 如何使用装饰器将变量注入范围?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17862185/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to inject variable into scope with a decorator?
提问by beardc
[Disclaimer: there may be more pythonic ways of doing what I want to do, but I want to know how python's scoping works here]
[免责声明:可能有更多的pythonic方法可以做我想做的事情,但我想知道python的范围在这里是如何工作的]
I'm trying to find a way to make a decorator that does something like injecting a name into the scope of another function (such that the name does not leak outside the decorator's scope). For example, if I have a function that says to print a variable named var
that has not been defined, I would like to define it within a decorator where it is called. Here is an example that breaks:
我试图找到一种方法来制作一个装饰器,它可以将名称注入另一个函数的范围内(这样名称不会泄漏到装饰器的范围之外)。例如,如果我有一个函数说要打印一个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()
I would like it to print "Message
", but it gives:
我希望它打印“ Message
”,但它给出:
NameError: global name 'var' is not defined
The traceback even points to wher var
is 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
So I don't understand why it can't find var
.
所以我不明白为什么它找不到var
.
Is there any way to do something like this?
有没有办法做这样的事情?
采纳答案by Martijn Pieters
You can't. Scoped names (closures) are determined at compile time, you cannot add more at runtime.
你不能。范围名称(闭包)在编译时确定,您不能在运行时添加更多。
The best you can hope to achieve is to add globalnames, using the function's ownglobal namespace:
您希望实现的最佳目标是使用函数自己的全局命名空间添加全局名称:
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__
is the global namespace for the wrapped function, so this works even if the decorator lives in a different module. If var
was defined as a global already, it is replaced with the new value, and after calling the function, the globals are restored.
f.__globals__
是包装函数的全局命名空间,因此即使装饰器位于不同的模块中,它也能工作。如果var
已经定义为全局变量,则将其替换为新值,并在调用该函数后恢复全局变量。
This works because any name in a function that is not assigned to, and is not found in a surrounding scope, is marked as a global instead.
这是有效的,因为函数中未分配给且未在周围范围中找到的任何名称都被标记为全局。
Demo:
演示:
>>> c = 'Message'
>>> @decorator_factory(c)
... def msg_printer():
... print var
...
>>> msg_printer()
Message
>>> 'var' in globals()
False
But instead of decorating, I could just as well have defined var
in the global scope directly.
但是,我可以直接var
在全局范围内定义,而不是装饰。
Note that altering the globals is not thread safe, and any transient calls to other functions in the same module will also still see this same global.
请注意,更改全局变量不是线程安全的,对同一模块中其他函数的任何瞬态调用也将仍然看到相同的全局变量。
回答by newacct
You can't. Python has lexical scoping. That means the meaning of an identifier is determined solely based on the scopes that physically surround it when you look at the source code.
你不能。Python 具有词法范围。这意味着标识符的含义完全取决于您查看源代码时物理上围绕它的范围。
回答by tlovely
Here is a simple demonstration of using a decorator to add a variable into the scope of a function.
这是使用装饰器将变量添加到函数作用域中的简单演示。
>>> 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!
>>>
回答by Alexander Otavka
Python is lexically scoped, so I'm afraid there is no clean way to do what you want without some potentially nasty side effects. I recommend just passing var into the function via the decorator.
Python 是词法范围的,所以恐怕没有一种干净的方法可以在没有一些潜在的讨厌的副作用的情况下做你想做的事情。我建议只通过装饰器将 var 传递给函数。
c = 'Message'
def decorator_factory(value):
def msg_decorator(f):
def inner_dec(*args, **kwargs):
res = f(value, *args, **kwargs)
return res
inner_dec.__name__ = 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'
回答by M07
There is a clean way to do what you want without using global variable. If you want to be stateless and threads safe, you don't really have the choice.
有一种干净的方法可以在不使用全局变量的情况下完成您想要的操作。如果你想要无状态和线程安全,你真的没有选择。
Use the "kwargs" variable:
使用“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()
回答by martineau
Here's a way of injecting multiplevariables into a function's scope in a manner somewhat similar to what @Martijn Pieters does in his answer. I'm posting it primarily because it's a more general solution and would notneed to be applied multiple times to do it — as would be required by his (and many of the other) answers.
这是一种以类似于@Martijn Pieters 在他的回答中所做的方式将多个变量注入函数范围的方法。我主要张贴,因为它是一个更广泛的解决方案,就不会需要使用多次这样做-正如他(和许多其他的)答案是必需的。
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 __name__ == '__main__':
namespace = {'a': 5, 'b': 3}
@inject_variables(namespace)
def test():
print('a:', a)
print('b:', b)
test()
回答by dAn
Assuming that in python functions are objects, you can do...
假设在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 __name__ == '__main__':
my_function(3, 4)
and the result is:
结果是:
inside class decorator >>
class members: 1, 2
wrapped function: (3, 4)
inside decorated function >>
decorated function arguments: 3, 4
decorator class args: (1, 2)
more explanation here http://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html
更多解释在这里http://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html
回答by Martin Wang
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.__name__ = 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)
Instead of revising the global scope, changing the annotated function itself is more reasonable.
与其修改全局作用域,不如改变注释函数本身更合理。
回答by weaming
Update __globals__
works for me.
更新__globals__
对我有用。
def f():
print(a)
def with_context(**kw):
def deco(fn):
g = fn.__globals__
g.update(kw)
return fn
return deco
with_context(a=3)(f)() # 3
回答by Artyom Lisovskij
I have catched problem with solution using globals.
我在使用全局变量的解决方案中遇到了问题。
Context of globals may be overwritten when you have several concurrent requests. I thought that impossible, but it is - after some time I have catched change of context(globals) if request wasn't quick. Better solution is to pass variable using kwargs:
当您有多个并发请求时,全局变量的上下文可能会被覆盖。我认为这是不可能的,但它是 - 一段时间后,如果请求不快,我就会发现上下文(全局)的变化。更好的解决方案是使用 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)
You'll have to add extra parameter to each decorated function.
您必须为每个装饰函数添加额外的参数。